Skip to content

feat: Add outputSchema to the tool definition - #208

Merged
kubinio123 merged 3 commits into
softwaremill:masterfrom
azhur:tool-output-schema
Aug 4, 2026
Merged

feat: Add outputSchema to the tool definition#208
kubinio123 merged 3 commits into
softwaremill:masterfrom
azhur:tool-output-schema

Conversation

@azhur

@azhur azhur commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

see #206

@kubinio123 kubinio123 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this @azhur!
It's a solid and tested code but I think this requires a little more work to get better type safety. Don't worry about backward incompatible updates, it's fine at this point, please see my comment below.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't commit the generated-docs files, the release process does that

def inputJson(schema: Json): Tool[Json] = Tool[Json](name, description, ToolSchema.Raw(schema), summon[Decoder[Json]], None, annotations)

/** A tool with a known input type `I`, ready to be given its handling logic. */
case class Tool[I](

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should reflect the structured output in tool types for compile time safety, like the following

tool("adder").input[In] // Tool[In, Unit]
    .handle(i => ToolResult.text("5")) // ok
    .handle(i => ToolResult.structured(Sum(5))) // does not compile

tool("adder").input[In].output[Sum] // Tool[In, Sum]
    .handle(i => ToolResult.structured(Sum(5))) // ok
    .handle(i => ToolResult.error("boom")) // ok
    .handle(i => ToolResult.text("5")) // does not compile

my suggestion will be for the Tool[I] to become Tool[I, O] where Tool[I, Unit] represents a tool without structured output.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the review and type-safety suggestion.

Done — Tool[I, O] as suggested, but with a dedicated NoOutput type instead of Unit, since Unit lets value discarding silently accept ToolResult.structured(x) (warning only, without -Werror) and ship structuredContent {}.

tool("adder").input[In].handle(i => ToolResult.structured(Sum(5))) // Tool[In, Unit]: Sum(5) discarded, structuredContent = {}

@azhur
azhur requested a review from kubinio123 August 3, 2026 14:45

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When result.content is missing but result.structuredContent is defined we should dump it's json text representation to content as MCP protocol recommends. Please also add a test for this case. https://modelcontextprotocol.io/specification/2026-07-28/server/tools#structured-content.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/** The structured output type of a tool that returns none. A distinct type rather than `Unit`, which value discarding would let any
* structured result conform to.
*/
sealed trait NoOutput

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we call it NoStructuredOutput?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was my initial choice but then i decided to make it shorter, renamed back to NoStructuredOutput

Comment thread README.md Outdated
backend.close()
```

### Structured output

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to keep README short, details are in the documentation, I think this can be removed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@azhur
azhur requested a review from kubinio123 August 4, 2026 07:31

@kubinio123 kubinio123 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @azhur 💯

@kubinio123
kubinio123 merged commit 4185be1 into softwaremill:master Aug 4, 2026
4 checks passed
@azhur
azhur deleted the tool-output-schema branch August 4, 2026 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No way to set a tool's outputSchema: ToolDefinition has the field, the server builder has no setter

2 participants