Skip to content

Add tool annotations and structured output#37

Open
itz4blitz wants to merge 3 commits into
tacticlaunch:mainfrom
itz4blitz:justin/tool-annotations
Open

Add tool annotations and structured output#37
itz4blitz wants to merge 3 commits into
tacticlaunch:mainfrom
itz4blitz:justin/tool-annotations

Conversation

@itz4blitz

@itz4blitz itz4blitz commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What

Adds MCP tool annotations and structured output to every tool the server exposes, so clients can auto-allow safe reads, gate destructive calls, and consume typed results.

  • Explicit annotations on all 185 tools. MCPToolDefinition now requires an annotations field, so the compiler enforces that every current and future tool ships a reviewed classification. Four factories in src/tool-annotations.ts (readOnlyToolAnnotations, additiveToolAnnotations, idempotentMutationToolAnnotations, destructiveToolAnnotations) each emit all four hints (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) with per-tool overrides available.
  • Output schemas on the wire. convertToolDefinition now advertises outputSchema for every tool, and successful tool results carry structuredContent alongside the existing JSON text payload (which is unchanged). Error results stay text-only with isError: true.

Classification rules

Category Rule Count
Read-only get*/search* reads; always idempotent 76
Idempotent mutations absolute setters and reversible membership/state changes (update*, assignIssue, setIssuePriority, add/removeIssueLabel, markNotificationAsRead, addToFavorites, completeCycle, unarchive*, ...) 55
Additive (non-idempotent) creators (create*), plus duplicateIssue and addAttachment, which mint a new entity per call 27
Destructive but idempotent delete*, archive*, logout* — repeating converges on the same end state 27

Every tool talks only to the configured Linear workspace, so openWorldHint is false across the board; both the unit tests and the smoke test pin that no tool advertises open-world behavior.

Array wrapping decision

MCP requires outputSchema and structuredContent to be top-level JSON objects, but many tools return arrays. Array-shaped output schemas are wrapped as { type: "object", properties: { items: <array schema> }, required: ["items"] }, and buildStructuredContent mirrors that wrapping on results ({ items: [...] }), so structuredContent always validates against the advertised schema. Object schemas pass through unchanged. The existing top-level input-schema sanitization (stripping oneOf/anyOf/allOf/enum/not) is preserved exactly.

Verification

  • jest: 26 suites, 186 tests passing, including a parity test that every definition declares complete boolean annotations (removing annotations from any definitions file fails the suite at compile time), classification spot tests, and wire-format tests for convertToolDefinition/buildStructuredContent.
  • npm run build: clean.
  • npm run test:mcp-smoke (real stdio round-trip): passes for 185 tools, 6 resources, 4 prompts — asserts every listed tool carries explicit boolean annotations and an object-typed outputSchema, linear_getIssues advertises the required items envelope, real local calls (linear_getServerStatus, linear_getRateLimitStatus) return structuredContent mirroring the JSON text payload (SDK-validated against the advertised schema), and error results stay text-only without structuredContent.
  • npm pack --dry-run and git diff --check: clean. No package version bump.

Based on #35 (MCP SDK 1.29) — required because the older SDK types lack annotations/outputSchema; contains its single commit until it merges.

@itz4blitz
itz4blitz marked this pull request as ready for review July 12, 2026 15:16
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.

1 participant