What happened?
read_skill is the only one of the nine MCP tools that sets CallToolResult.Content itself:
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: content}},
}, types.ReadSkillOutput{Instructions: content}, nil
The SDK then unconditionally populates StructuredContent from the typed output, so the response carries the skill body twice — once as raw markdown in content[0].text, once JSON-escaped inside structuredContent.instructions.
The other eight handlers return nil, output, nil and also carry their payload twice, but that is the spec's backward-compat duplicate and both copies are identical JSON. read_skill is different: the duplication is hand-written, and the two copies are different encodings of the same bytes.
Measured against the embedded skills, wire bytes per call:
| path |
file |
wire |
ratio |
SKILL.md |
800 B |
1723 B |
2.15× |
error-root-cause/SKILL.md |
1427 B |
3021 B |
2.12× |
detect-n-plus-one/SKILL.md |
1535 B |
3243 B |
2.11× |
INSTRUCTIONS.md points agents at SKILL.md first, so a skill-driven run pays this on at least two calls before it touches any telemetry.
Steps to reproduce
- Call
read_skill with path: "SKILL.md" over the MCP endpoint.
- Compare
content[0].text with structuredContent.instructions — same body, two encodings.
Expected behavior
One copy. Which one is a design call, so I'd rather ask than guess. Measured on a 347-byte skill:
| option |
wire |
note |
| today |
797 B |
— |
drop the hand-set Content |
832 B |
worse — content becomes the JSON copy |
Content: []mcp.Content{} |
413 B |
one copy, but no content block for clients that only read content |
markdown in Content, metadata (path, truncated) in the typed output |
473 B |
keeps readable markdown and a content block |
The last one looks best to me — it keeps the agent-facing markdown unescaped and drops the body from the structured half — but it changes ReadSkillOutput's schema, so it's your call. Happy to implement whichever you prefer, with a test pinning the wire shape (the current tests assert output.Instructions only, which is why this went unnoticed).
Worth noting the same 2× applies to the other eight tools; that half is inherited from the spec's backward-compat guidance, so I've left it out of this issue and raised the measurement side in #9135 instead.
Relevant log output
No response
Screenshot
No response
Additional context
No response
Jaeger backend version
main (2487d69)
SDK
Not applicable — this is in the MCP server handler layer (mcptools), not ingestion.
Pipeline
Not applicable.
Stogage backend
Not applicable.
What happened?
read_skillis the only one of the nine MCP tools that setsCallToolResult.Contentitself:The SDK then unconditionally populates
StructuredContentfrom the typed output, so the response carries the skill body twice — once as raw markdown incontent[0].text, once JSON-escaped insidestructuredContent.instructions.The other eight handlers return
nil, output, niland also carry their payload twice, but that is the spec's backward-compat duplicate and both copies are identical JSON.read_skillis different: the duplication is hand-written, and the two copies are different encodings of the same bytes.Measured against the embedded skills, wire bytes per call:
SKILL.mderror-root-cause/SKILL.mddetect-n-plus-one/SKILL.mdINSTRUCTIONS.mdpoints agents atSKILL.mdfirst, so a skill-driven run pays this on at least two calls before it touches any telemetry.Steps to reproduce
read_skillwithpath: "SKILL.md"over the MCP endpoint.content[0].textwithstructuredContent.instructions— same body, two encodings.Expected behavior
One copy. Which one is a design call, so I'd rather ask than guess. Measured on a 347-byte skill:
Contentcontentbecomes the JSON copyContent: []mcp.Content{}contentContent, metadata (path, truncated) in the typed outputThe last one looks best to me — it keeps the agent-facing markdown unescaped and drops the body from the structured half — but it changes
ReadSkillOutput's schema, so it's your call. Happy to implement whichever you prefer, with a test pinning the wire shape (the current tests assertoutput.Instructionsonly, which is why this went unnoticed).Worth noting the same 2× applies to the other eight tools; that half is inherited from the spec's backward-compat guidance, so I've left it out of this issue and raised the measurement side in #9135 instead.
Relevant log output
No response
Screenshot
No response
Additional context
No response
Jaeger backend version
main (2487d69)
SDK
Not applicable — this is in the MCP server handler layer (
mcptools), not ingestion.Pipeline
Not applicable.
Stogage backend
Not applicable.