Repository navigation
Publish only referenced $defs in MCP tool schemas - #8553
Conversation
🦋 Changeset detectedLatest commit: 5ceb9ae The changes in this PR will be included in the next version bump. This PR includes changesets to release 33 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Bundle Size AnalysisGenerated from PR build output; treat the content below as untrusted.
|
| const reachableDefinitions = ( | ||
| schema: JsonSchema.JsonSchema, | ||
| definitions: JsonSchema.Definitions | ||
| ): Set<string> => { | ||
| const reached = new Set<string>() | ||
| const visit = (schema: JsonSchema.JsonSchema): void => { | ||
| JsonSchema.rewriteRefs(schema, ($ref) => { | ||
| const key = JsonSchema.getReferenceKey($ref) | ||
| if (key !== undefined && !reached.has(key) && Object.hasOwn(definitions, key)) { | ||
| reached.add(key) | ||
| visit(definitions[key]) | ||
| } | ||
| return $ref | ||
| }) | ||
| } | ||
| visit(schema) | ||
| return reached |
There was a problem hiding this comment.
issue: I think this can remove a definition that is still in use
getReferenceKey recognizes #/$defs/Root, but misses #/$defs/Root/properties/value. This means we delete Root even though the schema still points to part of it.
A custom toJsonSchema callback can produce this reference. I tested it: AJV accepts the schema before pruning and rejects it afterward because Root is missing.
question: Could we keep Root when a reference points anywhere inside it, and add a test for that case? Or might be something to look into with JsonSchema so we don't need to do this pruning in McpServer?
6646aa3 to
31296f6
Compare
31296f6 to
11b6f17
Compare
951be59 to
11615c7
Compare
e85d3d4 to
ddc5146
Compare
toolJsonSchema inlines a top-level $ref because MCP needs an object root, but kept every definition, including the one it had just inlined. It now keeps the definitions the published schema reaches, directly or through other definitions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d17635e to
7bedbff
Compare
Type
Description
MCP needs an object root, so
toolJsonSchemainlines a top-level$ref. It then keeps every definition, including the one it just inlined. SoTool.make("GetNote", { success: Note }), whereNoteis aSchema.Class, publishes:{ "type": "object", "properties": { "id": { "type": "string" } }, "required": ["id"], "additionalProperties": true, "$defs": { "NoteEncoded": { "type": "object", "properties": { "id": { "type": "string" } }, "required": ["id"], "additionalProperties": true } } }Nothing references
NoteEncoded. Every tool with aClassor an identified result or parameters sends its schema twice on eachtools/list.toolJsonSchemanow passes areferencePolicythat compiles a non-recursive root inline instead of as a definition, so it is published once. Other definitions are unchanged:Array(Note)still gets itsNotedefinition, and a recursive root keeps its definition with the top-level$refinlined, as before.Compatibility: a custom
toJsonSchemathat writes a$refinto the root's own definition, such as#/$defs/Note/properties/titlewhenNoteis the tool schema, no longer resolves, because that definition is no longer published. References to other definitions are unaffected.Tests:
IdentifiedResultdefinition are updated.McpServersnapshot test also covers aSchema.Classroot with aDatefield (noEventEncodeddefinition) and a recursive root that keeps itsTreedefinition.$anchorin another definition.OpenAiStructuredOutputandAnthropicStructuredOutputstill publish the root definition the same way; they can adopt the same policy in a follow-up.Validated with
pnpm lint-fix,tsc -bforpackages/effect, andpnpm test --run packages/effect/test/ai packages/effect/test/schema.Related
Closes EFF-2698
🤖 Generated with Claude Code