diff --git a/docs/mcp/harnesses.md b/docs/mcp/harnesses.md index 5c0e80e23..d141884de 100644 --- a/docs/mcp/harnesses.md +++ b/docs/mcp/harnesses.md @@ -88,9 +88,11 @@ Only tool names and server instructions load at session start; the model calls - `ENABLE_TOOL_SEARCH` accepts `true`, `false`, `auto`, and `auto:N`. - Older models load everything up front. So do sessions with `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS`, HIPAA sessions, and Azure Foundry. -- A server can keep chosen tools loaded with `_meta["anthropic/alwaysLoad"]`, - for example `get_sentry_resource`. The key is specific to Claude Code, and - long-term support is uncertain. +- A server can keep chosen tools loaded with `_meta["anthropic/alwaysLoad"]`. + We set it on `get_sentry_resource` only, through the tool's `_meta` config, + so a pasted Sentry URL works without a tool search first. The key is specific + to Claude Code, and long-term support is uncertain. A user's server-level + `alwaysLoad` setting can override it. - Description caps: 4,096 characters for upfront tools and server instructions, 16,384 for deferred tools. - No fixed tool cap; the context window is the limit. diff --git a/packages/mcp-core/scripts/measure-token-cost.ts b/packages/mcp-core/scripts/measure-token-cost.ts index 2807e2f67..9e1c07289 100644 --- a/packages/mcp-core/scripts/measure-token-cost.ts +++ b/packages/mcp-core/scripts/measure-token-cost.ts @@ -67,6 +67,7 @@ type ToolDefinition = { idempotentHint?: boolean; openWorldHint?: boolean; }; + _meta?: Record; }; /** @@ -94,6 +95,7 @@ function formatToolsForMCP(tools: Record) { }), inputSchema: jsonSchema, ...(tool.annotations && { annotations: tool.annotations }), + ...(tool._meta && { _meta: tool._meta }), }; }); } diff --git a/packages/mcp-core/src/server.all-tools.test.ts b/packages/mcp-core/src/server.all-tools.test.ts index 4902ffbdd..09c4c955b 100644 --- a/packages/mcp-core/src/server.all-tools.test.ts +++ b/packages/mcp-core/src/server.all-tools.test.ts @@ -97,6 +97,19 @@ describe.skipIf(!EXPOSE_ALL_TOOLS)("buildServer with EXPOSE_ALL_TOOLS", () => { } }); + it("asks Claude Code to load only get_sentry_resource upfront", async () => { + const listed = await withClient( + getContext(), + async (client) => (await client.listTools()).tools, + ); + + expect( + listed + .filter((tool) => tool._meta) + .map((tool) => [tool.name, tool._meta]), + ).toEqual([["get_sentry_resource", { "anthropic/alwaysLoad": true }]]); + }); + it("lists the replacements of deprecated tools instead of the tools", async () => { const toolNames = await listToolNames(getContext()); const deprecatedTools = Object.values(tools).filter( diff --git a/packages/mcp-core/src/server.ts b/packages/mcp-core/src/server.ts index 70a57d996..875b0ec74 100644 --- a/packages/mcp-core/src/server.ts +++ b/packages/mcp-core/src/server.ts @@ -401,6 +401,7 @@ function configureServer({ inputSchema: filteredInputSchema, outputSchema: tool.outputSchema, annotations: tool.annotations, + _meta: tool._meta, }; const handleToolCall = async (params: unknown): Promise => { // Get the active MCP server span and attach request-scoped attributes. diff --git a/packages/mcp-core/src/tools/catalog/get-sentry-resource.ts b/packages/mcp-core/src/tools/catalog/get-sentry-resource.ts index 1f78bcfa3..c381d7534 100644 --- a/packages/mcp-core/src/tools/catalog/get-sentry-resource.ts +++ b/packages/mcp-core/src/tools/catalog/get-sentry-resource.ts @@ -620,6 +620,9 @@ export default defineTool({ destructiveHint: false, openWorldHint: true, }, + // Users often start from a pasted Sentry URL, so Claude Code keeps this tool + // loaded instead of deferring it behind tool search. + _meta: { "anthropic/alwaysLoad": true }, async handler(params, context: ServerContext) { const url = params.url ?? params.issueUrl ?? params.resourceUrl; diff --git a/packages/mcp-core/src/tools/types.ts b/packages/mcp-core/src/tools/types.ts index 374831db8..c365e27cb 100644 --- a/packages/mcp-core/src/tools/types.ts +++ b/packages/mcp-core/src/tools/types.ts @@ -149,6 +149,11 @@ export interface ToolConfig< idempotentHint?: boolean; openWorldHint: boolean; }; + /** + * Client-specific tool metadata exported as `_meta` in tools/list, such as + * `anthropic/alwaysLoad` (see docs/mcp/harnesses.md). + */ + _meta?: Record; handler: ToolHandler>; /** * Optional hook invoked when the handler throws, for tool-specific failure