From 2838aeff932cf9bbd35bfb8dd3c157ac4896698e Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Wed, 21 Oct 2026 15:18:56 -0400 Subject: [PATCH 1/2] feat!: move to MCP TypeScript SDK v2 (#245) Swaps @modelcontextprotocol/sdk 1.x for the v2 packages (@modelcontextprotocol/server and /core, plus /client and /node for tests and scripts). Ran the official v1-to-v2 codemod, then: - Replace the SDK output-validation patch with lenientOutputSchema, a Standard Schema wrapper that keeps the real schema in tools/list but only warns on a mismatch. Tools that return text only get structuredContent {}. - Make publishJsonSchema2020 a deprecated no-op, since v2 publishes JSON Schema 2020-12 natively. Kept for hosted's adapter. - Remove OptimizationV2Tool, which was never registered and used the experimental tasks API that v2 drops. - Move test request contexts to the v2 shape (http.authInfo) so the token checks actually run. - Replace the patch tests, which never went through the SDK, with tests that make a real tools/call. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 11 + docs/importing-tools.md | 20 +- evals/tool-selection/harness.ts | 5 +- examples/import-example.ts | 11 +- package-lock.json | 861 ++---------------- package.json | 7 +- .../@modelcontextprotocol+sdk+1.30.0.patch | 66 -- patches/README.md | 44 +- scripts/dev-http-server.ts | 6 +- src/index.ts | 24 +- src/prompts/BasePrompt.ts | 10 +- src/prompts/FindPlacesNearbyPrompt.ts | 2 +- src/prompts/GetDirectionsPrompt.ts | 2 +- src/prompts/SearchAlongRoutePrompt.ts | 2 +- src/prompts/ShowReachableAreasPrompt.ts | 2 +- src/resources/BaseResource.ts | 27 +- src/resources/MapboxApiBasedResource.ts | 15 +- .../category-list/CategoryListResource.ts | 2 +- src/resources/compute/ComputeResource.ts | 8 +- .../inline-payload/InlinePayloadResource.ts | 8 +- .../place-photo/PlacePhotoResource.ts | 2 +- .../temporary/TemporaryDataResource.ts | 13 +- .../tool-response/ToolResponseResource.ts | 2 +- src/resources/ui-apps/MapAppUIResource.ts | 13 +- src/resources/version/VersionResource.ts | 2 +- src/tools/BaseTool.ts | 43 +- src/tools/MapboxApiBasedTool.ts | 16 +- src/tools/area-tool/AreaTool.ts | 2 +- src/tools/bearing-tool/BearingTool.ts | 2 +- .../bounding-box-tool/BoundingBoxTool.ts | 2 +- src/tools/buffer-tool/BufferTool.ts | 2 +- .../category-list-tool/CategoryListTool.ts | 2 +- .../CategorySearchTool.ts | 2 +- src/tools/centroid-tool/CentroidTool.ts | 2 +- src/tools/convex-tool/ConvexTool.ts | 2 +- src/tools/destination-tool/DestinationTool.ts | 2 +- src/tools/difference-tool/DifferenceTool.ts | 2 +- src/tools/directions-tool/DirectionsTool.ts | 2 +- src/tools/distance-tool/DistanceTool.ts | 2 +- src/tools/feedback-tool/FeedbackTool.ts | 2 +- .../GroundLocationTool.ts | 2 +- src/tools/intersect-tool/IntersectTool.ts | 2 +- src/tools/isochrone-tool/IsochroneTool.ts | 2 +- src/tools/length-tool/LengthTool.ts | 2 +- src/tools/lenientOutputSchema.ts | 41 + .../map-matching-tool/MapMatchingTool.ts | 2 +- src/tools/matrix-tool/MatrixTool.ts | 2 +- src/tools/midpoint-tool/MidpointTool.ts | 2 +- .../NearestPointOnLineTool.ts | 2 +- .../nearest-point-tool/NearestPointTool.ts | 2 +- .../optimization-tool/OptimizationTool.ts | 2 +- .../OptimizationV2Tool.input.schema.ts | 215 ----- .../OptimizationV2Tool.output.schema.ts | 139 --- .../optimization-tool/OptimizationV2Tool.ts | 442 --------- .../place-details-tool/PlaceDetailsTool.ts | 2 +- .../PointsWithinPolygonTool.ts | 2 +- src/tools/render-map-tool/RenderMapTool.ts | 12 +- .../ResourceReaderTool.ts | 14 +- .../ReverseGeocodeTool.ts | 2 +- .../SearchAndGeocodeTool.ts | 2 +- src/tools/simplify-tool/SimplifyTool.ts | 2 +- .../StaticMapImageTool.ts | 2 +- src/tools/union-tool/UnionTool.ts | 2 +- src/tools/validateOutput.ts | 2 +- src/utils/index.ts | 4 +- src/utils/jsonSchema2020.ts | 51 +- src/utils/versionUtils-cjs.cjs | 92 +- src/utils/versionUtils-cjs.d.cts | 12 +- test/exports.test.ts | 8 +- .../hostedMultiTaskFollowup.test.ts | 4 +- test/integration/processRestart.test.ts | 4 +- test/patches/mcp-sdk-patch.test.ts | 282 ------ test/prompts/BasePrompt.test.ts | 2 +- test/resources/BaseResource.test.ts | 2 +- .../resources/compute/ComputeResource.test.ts | 2 +- .../temporary/TemporaryDataResource.test.ts | 2 +- .../ui-apps/MapAppUIResource.test.ts | 4 +- test/tools/BaseTool.test.ts | 2 +- test/tools/annotations.test.ts | 2 +- .../CategorySearchTool.test.ts | 2 +- .../difference-tool/DifferenceTool.test.ts | 2 +- .../directions-tool/DirectionsTool.test.ts | 6 +- .../GroundLocationTool.test.ts | 2 +- .../intersect-tool/IntersectTool.test.ts | 2 +- .../isochrone-tool/IsochroneTool.test.ts | 2 +- test/tools/lenientOutputSchema.test.ts | 130 +++ .../map-matching-tool/MapMatchingTool.test.ts | 2 +- .../OptimizationTool.test.ts | 2 +- .../render-map-tool/RenderMapTool.test.ts | 20 +- .../SearchAndGeocodeTool.test.ts | 2 +- test/tools/union-tool/UnionTool.test.ts | 2 +- test/utils/jsonSchema2020.test.ts | 27 +- 92 files changed, 546 insertions(+), 2277 deletions(-) delete mode 100644 patches/@modelcontextprotocol+sdk+1.30.0.patch create mode 100644 src/tools/lenientOutputSchema.ts delete mode 100644 src/tools/optimization-tool/OptimizationV2Tool.input.schema.ts delete mode 100644 src/tools/optimization-tool/OptimizationV2Tool.output.schema.ts delete mode 100644 src/tools/optimization-tool/OptimizationV2Tool.ts delete mode 100644 test/patches/mcp-sdk-patch.test.ts create mode 100644 test/tools/lenientOutputSchema.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1541150d..19f28c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ ## Unreleased +### Breaking Changes + +- **MCP TypeScript SDK v2 (#245).** The server and the `@mapbox/mcp-server/tools`, `/resources` and `/utils` exports now use the v2 SDK packages (`@modelcontextprotocol/server` and `@modelcontextprotocol/core`, 2.3.1) instead of `@modelcontextprotocol/sdk` 1.x, and `@modelcontextprotocol/ext-apps` moves from 1.x to 2.0.3. v2 is the line that gets new MCP features, such as the SDK's Skills extension support, and ext-apps 2.x requires it. + - **For code that imports our tools:** `installTo(server)` needs a v2 `McpServer`. A v1 server from `@modelcontextprotocol/sdk` won't work, so a deployment that builds its own server (the hosted server, for one) has to move to v2 in the same release. Tool and resource handlers now receive the v2 context: the access token is at `ctx.http.authInfo.token` instead of `extra.authInfo.token`. + - **Clients:** nothing changes. The server still answers the `initialize` handshake and negotiates 2025-11-25 with current clients, and the MCP Apps wire protocol is the same in ext-apps 2.x, so `render_map_tool` renders as before. Adopting the 2026-07-28 protocol revision (MRTR in place of elicitation, the Sampling replacement) is separate work under #245. + - **No more SDK patch:** the v1 SDK was patched so a tool's `structuredContent` that didn't match its output schema, or was missing, logged a warning instead of failing the call. v2 takes any Standard Schema as an output schema, so `lenientOutputSchema` wraps each tool's Zod schema: `tools/list` still publishes the real JSON Schema, and validation only warns. A tool with an output schema that returns only text gets `structuredContent: {}` instead of an error, since v2 checks for it before validating. `patches/` is now empty. + - **JSON Schema 2020-12:** v2 publishes tool schemas as 2020-12 itself, so `publishJsonSchema2020(server)` is now a deprecated no-op, kept so existing callers don't break. `toJsonSchema2020` is unchanged. + - **Removed:** `OptimizationV2Tool`, which was never registered and was built on the SDK's experimental tasks API, which v2 removes. + - **Codemod:** most of the change is `npx @modelcontextprotocol/codemod v1-to-v2 .` (import paths, method-string `setRequestHandler`, `RequestHandlerExtra` to `ServerContext`, `extra.authInfo` to `ctx.http?.authInfo`). `@modelcontextprotocol/client` and `@modelcontextprotocol/node` are dev dependencies, used only by tests and scripts. + - **Testing:** the old patch tests called `tool.run()` directly and never went through the SDK; they're replaced by tests that make a real `tools/call` through `McpServer` for a mismatched, a missing and a matching `structuredContent`, and they fail without the wrapper. Tests that built a request context by hand used the v1 `{ authInfo }` shape, so the token never reached the code under test; they now use `{ http: { authInfo } }`, which also makes the map app's secret-token checks test what they say. A stdio smoke test against the built server covers the handshake, all 30 tools with 2020-12 schemas, prompts, completions, resources, the MCP Apps UI resource, logging and a tool call. + ### New Features - **`feedback_tool`: report wrong map, place or routing data to Mapbox (#291).** When someone says a place has closed, a road is closed, or a route took an illegal turn, the agent can send it to the [Feedback API](https://docs.mapbox.com/api/feedback/) (`POST /user-feedback/v1/feedback`, Public Preview) with the text, a category and the location, usually taken from an earlier search or directions result. It returns the report's id and status (`received`). diff --git a/docs/importing-tools.md b/docs/importing-tools.md index 8b982b9d..7e3c253e 100644 --- a/docs/importing-tools.md +++ b/docs/importing-tools.md @@ -332,15 +332,12 @@ import type { PromptInstance } from '@mapbox/mcp-server/prompts'; Here's how you might use these exports to build your own MCP server with a subset of tools. Each tool and resource registers itself with `installTo(server)`: ```typescript -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { McpServer } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; import { getAllTools } from '@mapbox/mcp-server/tools'; import { getAllResources } from '@mapbox/mcp-server/resources'; -import { - buildServerInstructions, - publishJsonSchema2020 -} from '@mapbox/mcp-server/utils'; +import { buildServerInstructions } from '@mapbox/mcp-server/utils'; // Pick only the tools you need. render_map_tool is only available through // the registry, and it needs the map UI resource from getAllResources(). @@ -363,11 +360,6 @@ const server = new McpServer( } ); -// Publish tool schemas as JSON Schema 2020-12 instead of the SDK's draft-07. -// Some clients (e.g. Claude Desktop) reject draft-07 output schemas. -// Must be called before the first tool is installed. -publishJsonSchema2020(server); - tools.forEach((tool) => tool.installTo(server)); getAllResources().forEach((resource) => resource.installTo(server)); @@ -378,9 +370,11 @@ await server.connect(transport); ### Server setup helpers -- `publishJsonSchema2020(server)`: converts every tool's `inputSchema`/`outputSchema` in `tools/list` from draft-07 to JSON Schema 2020-12. Call it on the `McpServer` before installing any tool. - `buildServerInstructions(toolNames)`: returns the server `instructions` text for the given enabled tool names, or `undefined` when `render_map_tool` isn't among them. -- `toJsonSchema2020(schema)`: the converter behind `publishJsonSchema2020`, for converting a single schema. +- `toJsonSchema2020(schema)`: converts a single draft-07 JSON Schema to JSON Schema 2020-12. +- `publishJsonSchema2020(server)`: deprecated no-op. MCP SDK v1 published tool schemas as draft-07 and this converted them; SDK v2 publishes 2020-12 itself, so the call can be removed. + +The tools and resources are built against MCP TypeScript SDK v2 (`@modelcontextprotocol/server`), so `installTo` needs a v2 `McpServer`. A v1 `McpServer` from `@modelcontextprotocol/sdk` won't work. ### Directions routes by reference diff --git a/evals/tool-selection/harness.ts b/evals/tool-selection/harness.ts index 1c6460d6..a206c4e5 100644 --- a/evals/tool-selection/harness.ts +++ b/evals/tool-selection/harness.ts @@ -19,9 +19,8 @@ import OpenAI from 'openai'; import { readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'; +import { Client } from '@modelcontextprotocol/client'; +import { McpServer, InMemoryTransport } from '@modelcontextprotocol/server'; const here = dirname(fileURLToPath(import.meta.url)); const SERVER_ROOT = resolve( diff --git a/examples/import-example.ts b/examples/import-example.ts index 357dbd13..7c19c1d5 100644 --- a/examples/import-example.ts +++ b/examples/import-example.ts @@ -8,8 +8,8 @@ * a custom MCP server with a subset of Mapbox tools. */ -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; +import { McpServer } from '@modelcontextprotocol/server'; // Import pre-configured tool instances import { @@ -48,9 +48,9 @@ async function createCustomMcpServer() { // Advanced usage: Create tools with custom HTTP pipeline async function createServerWithCustomPipeline() { - const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js'); + const { McpServer } = await import('@modelcontextprotocol/server'); const { StdioServerTransport } = - await import('@modelcontextprotocol/sdk/server/stdio.js'); + await import('@modelcontextprotocol/server/stdio'); const { DirectionsTool } = await import('@mapbox/mcp-server/tools'); const { HttpPipeline, UserAgentPolicy, RetryPolicy } = await import('@mapbox/mcp-server/utils'); @@ -107,8 +107,7 @@ async function createServerWithCustomPipeline() { // Selective tool imports: Import only what you need to keep bundle size small async function selectiveImports() { - const { McpServer: Server } = - await import('@modelcontextprotocol/sdk/server/mcp.js'); + const { McpServer: Server } = await import('@modelcontextprotocol/server'); // Import just the Turf.js geometry tools (no HTTP calls needed) const { area, distance, bearing, centroid } = diff --git a/package-lock.json b/package-lock.json index 46e4392d..28a61d10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,9 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@modelcontextprotocol/ext-apps": "^1.1.1", - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/core": "^2.3.1", + "@modelcontextprotocol/ext-apps": "^2.0.3", + "@modelcontextprotocol/server": "^2.3.1", "@opentelemetry/api": "^1.9.1", "@opentelemetry/auto-instrumentations-node": "^0.74.0", "@opentelemetry/exporter-trace-otlp-http": "^0.216.0", @@ -31,6 +32,8 @@ "devDependencies": { "@anthropic-ai/sdk": "^0.133.0", "@eslint/js": "^9.27.0", + "@modelcontextprotocol/client": "^2.3.1", + "@modelcontextprotocol/node": "^2.1.1", "@types/node": "^22.0.0", "@typescript-eslint/eslint-plugin": "^8.58.2", "@typescript-eslint/parser": "^8.58.2", @@ -1748,9 +1751,10 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "version": "1.19.17", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.17.tgz", + "integrity": "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1913,24 +1917,62 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@modelcontextprotocol/client": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.3.1.tgz", + "integrity": "sha512-mIGZXpHsjnZ6lD+gD/WCMpR5k8yVQQ8nNFH1N0Srf7AvnwTUMYD6pvTY8ng2+He5FfV7YMZLmrjL7cLa/cc3dQ==", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/core": "2.3.1", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.8", + "jose": "^6.1.3", + "pkce-challenge": "^5.0.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/core": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.3.1.tgz", + "integrity": "sha512-laVmIhPGpWi7rG9q+0UigblRN0Hws/Yjlb6qLFtVqryXVLkc4h+yN8z731POD2LnAGQlFe7/tXhUm7Bi0YB91g==", + "license": "Apache-2.0", + "dependencies": { + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", - "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-2.0.3.tgz", + "integrity": "sha512-Tn+4+cyhO4f4cQSDxNyLiIfEE+qlTTuIbWBJB8JTinEXOhDYgCT4wDTQplxD9MBYZH5pCYGC59GaxiwIT4bu8w==", "license": "MIT", "workspaces": [ "examples/*" ], + "dependencies": { + "@standard-schema/spec": "^1.1.0" + }, "engines": { "node": ">=20" }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/client": "^2.0.0", + "@modelcontextprotocol/core": "^2.0.0", + "@modelcontextprotocol/server": "^2.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", - "zod": "^3.25.0 || ^4.0.0" + "zod": "^4.2.0" }, "peerDependenciesMeta": { + "@modelcontextprotocol/server": { + "optional": true + }, "react": { "optional": true }, @@ -1939,44 +1981,34 @@ } } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", - "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", - "license": "MIT", + "node_modules/@modelcontextprotocol/node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/node/-/node-2.1.1.tgz", + "integrity": "sha512-EY92OAXN2xYL7tNS/MVsWJW1v8VHAPwl9B4Ca3pCrjmvqyC+S1C/6UHsMxF2GoFPraS5Yyg56vFz9brVpFojMA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@hono/node-server": "^1.19.9 || ^2.0.5", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" + "@hono/node-server": "^1.19.9", + "hono": "^4.11.4" }, "engines": { - "node": ">=18" + "node": ">=20" }, "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" + "@modelcontextprotocol/server": "^2.3.0" + } + }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.3.1.tgz", + "integrity": "sha512-e59MfWuSssj6DoQ96JCTqdW1AXT5/XtnTl0WWd6MU1Ka434fFUGGosCLJbGNySDhHGvxQWoRWK137FXYFkYNWw==", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/core": "2.3.1", + "zod": "^4.2.0" }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } + "engines": { + "node": ">=20" } }, "node_modules/@napi-rs/wasm-runtime": { @@ -3704,7 +3736,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, "license": "MIT" }, "node_modules/@turf/along": { @@ -6453,19 +6484,6 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "license": "BSD-2-Clause" }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -6506,39 +6524,6 @@ "node": ">= 14" } }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/ansi-escapes": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", @@ -6688,30 +6673,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -6783,15 +6744,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -7194,28 +7146,6 @@ "tinyqueue": "^2.0.3" } }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -7223,41 +7153,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -7541,15 +7436,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -7597,12 +7483,6 @@ "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", "license": "ISC" }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, "node_modules/electron-to-chromium": { "version": "1.5.336", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.336.tgz", @@ -7616,15 +7496,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/enhanced-resolve": { "version": "5.20.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", @@ -7756,12 +7627,6 @@ "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -8227,15 +8092,6 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/eventemitter3": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", @@ -8256,9 +8112,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -8287,67 +8143,6 @@ "node": ">=12.0.0" } }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", - "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", - "license": "MIT", - "dependencies": { - "ip-address": "10.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -8398,22 +8193,6 @@ "dev": true, "license": "Unlicense" }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -8480,27 +8259,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -8626,30 +8384,12 @@ "node": ">=12.20.0" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/forwarded-parse": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", "license": "MIT" }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -9065,9 +8805,10 @@ } }, "node_modules/hono": { - "version": "4.12.12", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", - "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", + "version": "4.13.13", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.13.tgz", + "integrity": "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==", + "dev": true, "license": "MIT", "engines": { "node": ">=16.9.0" @@ -9080,26 +8821,6 @@ "dev": true, "license": "MIT" }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -9133,6 +8854,7 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -9256,6 +8978,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -9397,15 +9120,6 @@ "node": ">=10.13.0" } }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/ipaddr.js": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", @@ -9528,12 +9242,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, "node_modules/is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -9754,18 +9462,6 @@ "node": ">=16" } }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" - }, "node_modules/json-stable-stringify": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", @@ -10394,27 +10090,6 @@ "node": ">= 0.4" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -10440,31 +10115,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -10614,15 +10264,6 @@ "dev": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -10697,27 +10338,6 @@ "dev": true, "license": "MIT" }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -10767,27 +10387,6 @@ ], "license": "MIT" }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/onetime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", @@ -11083,15 +10682,6 @@ "node": ">=0.10.0" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/patch-package": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", @@ -11201,16 +10791,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -11475,28 +11055,6 @@ "node": ">=12.0.0" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -11507,51 +11065,12 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/quickselect": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", "license": "ISC" }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/rbush": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", @@ -11619,15 +11138,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-in-the-middle": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", @@ -11888,22 +11398,6 @@ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/run-async": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", @@ -11949,6 +11443,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, "license": "MIT" }, "node_modules/semver": { @@ -11963,51 +11458,6 @@ "node": ">=10" } }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -12025,12 +11475,6 @@ "node": ">= 0.4" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -12052,78 +11496,6 @@ "node": ">=8" } }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -12233,15 +11605,6 @@ "fast-sha256": "^1.3.0" } }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/std-env": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", @@ -12524,15 +11887,6 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/topojson-client": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", @@ -12709,20 +12063,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typescript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", @@ -12800,15 +12140,6 @@ "node": ">= 10.0.0" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -12867,15 +12198,6 @@ "node": ">= 10.13.0" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vite": { "version": "8.0.8", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", @@ -13179,12 +12501,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, "node_modules/xdg-basedir": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", @@ -13343,15 +12659,6 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } } } } diff --git a/package.json b/package.json index 6b53b409..7d953a1d 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,8 @@ "devDependencies": { "@anthropic-ai/sdk": "^0.133.0", "@eslint/js": "^9.27.0", + "@modelcontextprotocol/client": "^2.3.1", + "@modelcontextprotocol/node": "^2.1.1", "@types/node": "^22.0.0", "@typescript-eslint/eslint-plugin": "^8.58.2", "@typescript-eslint/parser": "^8.58.2", @@ -85,8 +87,9 @@ "mcp" ], "dependencies": { - "@modelcontextprotocol/ext-apps": "^1.1.1", - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/core": "^2.3.1", + "@modelcontextprotocol/ext-apps": "^2.0.3", + "@modelcontextprotocol/server": "^2.3.1", "@opentelemetry/api": "^1.9.1", "@opentelemetry/auto-instrumentations-node": "^0.74.0", "@opentelemetry/exporter-trace-otlp-http": "^0.216.0", diff --git a/patches/@modelcontextprotocol+sdk+1.30.0.patch b/patches/@modelcontextprotocol+sdk+1.30.0.patch deleted file mode 100644 index 3471ba41..00000000 --- a/patches/@modelcontextprotocol+sdk+1.30.0.patch +++ /dev/null @@ -1,66 +0,0 @@ -diff --git a/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js b/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js -index 9e1c874..2de8d57 100644 ---- a/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js -+++ b/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js -@@ -197,15 +197,20 @@ class McpServer { - return; - } - if (!result.structuredContent) { -- throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Output validation error: Tool ${toolName} has an output schema but no structured content was provided`); -+ // Log warning but don't throw - allow tools to omit structured content -+ console.warn(`[MCP SDK Patch] Output validation warning: Tool ${toolName} has an output schema but no structured content was provided`); - } -- // if the tool has an output schema, validate structured content -- const outputObj = (0, zod_compat_js_1.normalizeObjectSchema)(tool.outputSchema); -- const parseResult = await (0, zod_compat_js_1.safeParseAsync)(outputObj, result.structuredContent); -- if (!parseResult.success) { -- const error = 'error' in parseResult ? parseResult.error : 'Unknown error'; -- const errorMessage = (0, zod_compat_js_1.getParseErrorMessage)(error); -- throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage}`); -+ else { -+ // if the tool has an output schema, validate structured content -+ const outputObj = (0, zod_compat_js_1.normalizeObjectSchema)(tool.outputSchema); -+ const parseResult = await (0, zod_compat_js_1.safeParseAsync)(outputObj, result.structuredContent); -+ if (!parseResult.success) { -+ const error = 'error' in parseResult ? parseResult.error : 'Unknown error'; -+ const errorMessage = (0, zod_compat_js_1.getParseErrorMessage)(error); -+ // Log warning but don't throw - allow schema mismatches -+ console.warn(`[MCP SDK Patch] Output validation warning: Invalid structured content for tool ${toolName}: ${errorMessage}`); -+ // Keep the structuredContent despite validation failure -+ } - } - } - /** -diff --git a/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js b/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js -index 2314d88..94aaef1 100644 ---- a/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js -+++ b/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js -@@ -194,15 +194,20 @@ export class McpServer { - return; - } - if (!result.structuredContent) { -- throw new McpError(ErrorCode.InvalidParams, `Output validation error: Tool ${toolName} has an output schema but no structured content was provided`); -+ // Log warning but don't throw - allow tools to omit structured content -+ console.warn(`[MCP SDK Patch] Output validation warning: Tool ${toolName} has an output schema but no structured content was provided`); - } -- // if the tool has an output schema, validate structured content -- const outputObj = normalizeObjectSchema(tool.outputSchema); -- const parseResult = await safeParseAsync(outputObj, result.structuredContent); -- if (!parseResult.success) { -- const error = 'error' in parseResult ? parseResult.error : 'Unknown error'; -- const errorMessage = getParseErrorMessage(error); -- throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage}`); -+ else { -+ // if the tool has an output schema, validate structured content -+ const outputObj = normalizeObjectSchema(tool.outputSchema); -+ const parseResult = await safeParseAsync(outputObj, result.structuredContent); -+ if (!parseResult.success) { -+ const error = 'error' in parseResult ? parseResult.error : 'Unknown error'; -+ const errorMessage = getParseErrorMessage(error); -+ // Log warning but don't throw - allow schema mismatches -+ console.warn(`[MCP SDK Patch] Output validation warning: Invalid structured content for tool ${toolName}: ${errorMessage}`); -+ // Keep the structuredContent despite validation failure -+ } - } - } - /** diff --git a/patches/README.md b/patches/README.md index 1a6f86d4..ce620a5e 100644 --- a/patches/README.md +++ b/patches/README.md @@ -1,41 +1,17 @@ -# MCP SDK Patches +# Dependency Patches -This directory contains patches for npm dependencies that are automatically applied after `npm install`. +Patches in this directory are applied to npm dependencies after `npm install` by [patch-package](https://github.com/ds300/patch-package) (see the `postinstall` script). There are none at the moment. -## @modelcontextprotocol/sdk +## History -**File:** `@modelcontextprotocol+sdk+1.21.1.patch` +### @modelcontextprotocol/sdk (retired) -**Purpose:** Makes MCP output schema validation non-fatal to improve resilience and user experience. +Until the move to MCP TypeScript SDK v2, a patch to `@modelcontextprotocol/sdk` made output schema validation non-fatal: when a tool's `structuredContent` didn't match its output schema, or a tool with an output schema returned none, the SDK logged a warning instead of failing the tool call. -**Problem:** The MCP SDK enforces strict output schema validation, which causes the entire tool call to fail when there are minor schema mismatches. This creates unnecessary risk and poor user experience - most MCP clients can gracefully handle responses that don't perfectly match the declared schema. +SDK v2 accepts any [Standard Schema](https://standardschema.dev/) as a tool's `outputSchema`, so the same behavior now comes from `src/tools/lenientOutputSchema.ts` (the published JSON Schema is unchanged, validation only warns) and `BaseTool.installTo` (a missing `structuredContent` is sent as `{}`), with no patch. `test/tools/lenientOutputSchema.test.ts` covers both through a real `tools/call`. -**Solution:** This patch modifies the MCP SDK to log warnings instead of throwing errors when output validation fails. This provides a better balance between: +## Adding a patch -- **Schema documentation** - Output schemas still serve as documentation for expected response structure -- **Resilience** - Tools continue working even with minor variations in response format -- **Observability** - Validation issues are logged for monitoring and debugging -- **User experience** - Clients receive the actual data instead of an error - -**Benefits:** - -- Prevents tool failures due to minor schema variations -- Allows graceful degradation when APIs evolve or return edge cases -- Maintains backward compatibility while improving reliability -- Clients that need strict validation can implement it themselves - -**Changes:** - -- Converts validation errors to console warnings with `[MCP SDK Patch]` prefix -- Allows tools to return structured content even when it doesn't match the schema exactly -- Preserves all existing functionality while removing unnecessary strictness - -**Maintenance:** - -- This patch is automatically applied after `npm install` via the `postinstall` script -- If the MCP SDK is updated, you may need to recreate this patch: - 1. Remove the old patch file - 2. Make the same modifications to the new SDK version - 3. Run `npx patch-package @modelcontextprotocol/sdk` - -**Philosophy:** This patch follows the robustness principle: "Be conservative in what you send, be liberal in what you accept." Output schemas remain valuable for documentation and tooling, but shouldn't cause failures when the real-world data varies slightly from the ideal schema. +1. Edit the file in `node_modules//`. +2. Run `npx patch-package `. +3. Commit the generated file in this directory and describe it here. diff --git a/scripts/dev-http-server.ts b/scripts/dev-http-server.ts index eb092202..a258562d 100644 --- a/scripts/dev-http-server.ts +++ b/scripts/dev-http-server.ts @@ -25,8 +25,8 @@ */ import { createServer } from 'node:http'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; +import { McpServer } from '@modelcontextprotocol/server'; import { registerAppResource, RESOURCE_MIME_TYPE @@ -112,7 +112,7 @@ const httpServer = createServer((req, res) => { // manual testing, no session/resumability concerns to worry about. void (async () => { const server = buildServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined }); try { diff --git a/src/index.ts b/src/index.ts index 5afb5e22..46c96a66 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,18 +8,12 @@ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { existsSync } from 'node:fs'; import { SpanStatusCode } from '@opentelemetry/api'; - -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; +import { McpServer } from '@modelcontextprotocol/server'; import { registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server'; -import { - ListPromptsRequestSchema, - GetPromptRequestSchema, - CompleteRequestSchema -} from '@modelcontextprotocol/sdk/types.js'; import { parseToolConfigFromArgs, filterTools } from './config/toolConfig.js'; import { getCoreTools, @@ -30,7 +24,6 @@ import { getAllResources } from './resources/resourceRegistry.js'; import { getAllPrompts, getPromptByName } from './prompts/promptRegistry.js'; import { completePromptArgument } from './completions/index.js'; import { getVersionInfo } from './utils/versionUtils.js'; -import { publishJsonSchema2020 } from './utils/jsonSchema2020.js'; import { buildServerInstructions } from './utils/serverInstructions.js'; import { handleCliMetadataArgs } from './cli.js'; import { @@ -136,9 +129,6 @@ const server = new McpServer( } ); -// Must run before the first tool is registered — see jsonSchema2020.ts. -publishJsonSchema2020(server); - // Register only core tools before connection // Capability-dependent tools will be registered dynamically after connection enabledCoreTools.forEach((tool) => { @@ -152,6 +142,10 @@ const regularResources = allResources.filter((r) => !r.uri.startsWith('ui://')); // Register MCP Apps UI resources using registerAppResource // IMPORTANT: Use RESOURCE_MIME_TYPE which is "text/html;profile=mcp-app" // This tells clients (like Claude Desktop) that this is an MCP App +// +// The casts are for the CommonJS build: there, ext-apps' declarations resolve +// McpServer from the SDK's ESM types and ours from its CJS types, so the +// same class doesn't type-check against itself. uiResources.forEach((resource) => { registerAppResource( // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -173,7 +167,7 @@ regularResources.forEach((resource) => { }); // Register prompt handlers -server.server.setRequestHandler(ListPromptsRequestSchema, async () => { +server.server.setRequestHandler('prompts/list', async () => { const allPrompts = getAllPrompts(); return { prompts: allPrompts.map((prompt) => prompt.getMetadata()) @@ -185,7 +179,7 @@ server.server.setRequestHandler(ListPromptsRequestSchema, async () => { // TODO: Remove this workaround when SDK fixes their type definitions // eslint-disable-next-line @typescript-eslint/no-explicit-any (server.server as any).setRequestHandler( - GetPromptRequestSchema, + 'prompts/get', async (request: any) => { const { name, arguments: args } = request.params; @@ -213,7 +207,7 @@ server.server.setRequestHandler(ListPromptsRequestSchema, async () => { // Register completion handler // eslint-disable-next-line @typescript-eslint/no-explicit-any (server.server as any).setRequestHandler( - CompleteRequestSchema, + 'completion/complete', // eslint-disable-next-line @typescript-eslint/no-explicit-any async (request: any) => { const { ref, argument } = request.params; diff --git a/src/prompts/BasePrompt.ts b/src/prompts/BasePrompt.ts index 70f1f284..71d13df1 100644 --- a/src/prompts/BasePrompt.ts +++ b/src/prompts/BasePrompt.ts @@ -1,11 +1,11 @@ // Copyright (c) Mapbox, Inc. // Licensed under the MIT License. -import { - type Prompt, - type PromptArgument, - type PromptMessage -} from '@modelcontextprotocol/sdk/types.js'; +import type { + Prompt, + PromptArgument, + PromptMessage +} from '@modelcontextprotocol/server'; /** * Base class for all MCP prompts. diff --git a/src/prompts/FindPlacesNearbyPrompt.ts b/src/prompts/FindPlacesNearbyPrompt.ts index cb9eb0fa..91fa774c 100644 --- a/src/prompts/FindPlacesNearbyPrompt.ts +++ b/src/prompts/FindPlacesNearbyPrompt.ts @@ -5,7 +5,7 @@ import { BasePrompt } from './BasePrompt.js'; import type { PromptArgument, PromptMessage -} from '@modelcontextprotocol/sdk/types.js'; +} from '@modelcontextprotocol/server'; /** * Prompt for finding places near a location with optional map visualization. diff --git a/src/prompts/GetDirectionsPrompt.ts b/src/prompts/GetDirectionsPrompt.ts index 888d19ff..51870d2b 100644 --- a/src/prompts/GetDirectionsPrompt.ts +++ b/src/prompts/GetDirectionsPrompt.ts @@ -5,7 +5,7 @@ import { BasePrompt } from './BasePrompt.js'; import type { PromptArgument, PromptMessage -} from '@modelcontextprotocol/sdk/types.js'; +} from '@modelcontextprotocol/server'; /** * Prompt for getting turn-by-turn directions between two locations. diff --git a/src/prompts/SearchAlongRoutePrompt.ts b/src/prompts/SearchAlongRoutePrompt.ts index b9364c35..d44958d1 100644 --- a/src/prompts/SearchAlongRoutePrompt.ts +++ b/src/prompts/SearchAlongRoutePrompt.ts @@ -5,7 +5,7 @@ import { BasePrompt } from './BasePrompt.js'; import type { PromptArgument, PromptMessage -} from '@modelcontextprotocol/sdk/types.js'; +} from '@modelcontextprotocol/server'; /** * Prompt for finding places along a route between two locations. diff --git a/src/prompts/ShowReachableAreasPrompt.ts b/src/prompts/ShowReachableAreasPrompt.ts index 0891c1ea..b3d9a2f8 100644 --- a/src/prompts/ShowReachableAreasPrompt.ts +++ b/src/prompts/ShowReachableAreasPrompt.ts @@ -5,7 +5,7 @@ import { BasePrompt } from './BasePrompt.js'; import type { PromptArgument, PromptMessage -} from '@modelcontextprotocol/sdk/types.js'; +} from '@modelcontextprotocol/server'; /** * Prompt for visualizing areas reachable within a specified time from a location. diff --git a/src/resources/BaseResource.ts b/src/resources/BaseResource.ts index 6c80beb1..e149ed93 100644 --- a/src/resources/BaseResource.ts +++ b/src/resources/BaseResource.ts @@ -2,16 +2,12 @@ // Licensed under the MIT License. import { AsyncLocalStorage } from 'node:async_hooks'; -import { - type McpServer, - ResourceTemplate -} from '@modelcontextprotocol/sdk/server/mcp.js'; -import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; +import { ResourceTemplate } from '@modelcontextprotocol/server'; import type { - ServerRequest, - ServerNotification, - ReadResourceResult -} from '@modelcontextprotocol/sdk/types.js'; + McpServer, + ReadResourceResult, + ServerContext +} from '@modelcontextprotocol/server'; /** * Base class for all MCP resources @@ -69,10 +65,10 @@ export abstract class BaseResource { ( uri: URL, _variables: Record, - extra: RequestHandlerExtra + ctx: ServerContext ) => this.invocationServer.run(server, () => - this.read(uri.toString(), extra) + this.read(uri.toString(), ctx) ) ); } else { @@ -80,12 +76,9 @@ export abstract class BaseResource { this.name, this.uri, metadata, - ( - uri: URL, - extra: RequestHandlerExtra - ) => + (uri: URL, ctx: ServerContext) => this.invocationServer.run(server, () => - this.read(uri.toString(), extra) + this.read(uri.toString(), ctx) ) ); } @@ -98,7 +91,7 @@ export abstract class BaseResource { */ abstract read( uri: string, - extra?: RequestHandlerExtra + extra?: ServerContext ): Promise; /** diff --git a/src/resources/MapboxApiBasedResource.ts b/src/resources/MapboxApiBasedResource.ts index 23801aa2..99a7e8d9 100644 --- a/src/resources/MapboxApiBasedResource.ts +++ b/src/resources/MapboxApiBasedResource.ts @@ -1,12 +1,10 @@ // Copyright (c) Mapbox, Inc. // Licensed under the MIT License. -import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; import type { - ServerRequest, - ServerNotification, - ReadResourceResult -} from '@modelcontextprotocol/sdk/types.js'; + ReadResourceResult, + ServerContext +} from '@modelcontextprotocol/server'; import { BaseResource } from './BaseResource.js'; import type { HttpRequest } from '../utils/types.js'; import { context, trace, SpanStatusCode } from '@opentelemetry/api'; @@ -54,12 +52,9 @@ export abstract class MapboxApiBasedResource extends BaseResource { /** * Validates and reads the resource. */ - async read( - uri: string, - extra?: RequestHandlerExtra - ): Promise { + async read(uri: string, extra?: ServerContext): Promise { // First check if token is provided via authentication context - const authToken = extra?.authInfo?.token; + const authToken = extra?.http?.authInfo?.token; const accessToken = authToken || MapboxApiBasedResource.mapboxAccessToken; if (!accessToken) { diff --git a/src/resources/category-list/CategoryListResource.ts b/src/resources/category-list/CategoryListResource.ts index 98fe3d64..0c901a9f 100644 --- a/src/resources/category-list/CategoryListResource.ts +++ b/src/resources/category-list/CategoryListResource.ts @@ -3,7 +3,7 @@ import { MapboxApiBasedResource } from '../MapboxApiBasedResource.js'; import type { HttpRequest } from '../../utils/types.js'; -import type { ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'; +import type { ReadResourceResult } from '@modelcontextprotocol/server'; // Interface for the full API response from Mapbox interface MapboxApiResponse { diff --git a/src/resources/compute/ComputeResource.ts b/src/resources/compute/ComputeResource.ts index 7bca720f..ec49d81a 100644 --- a/src/resources/compute/ComputeResource.ts +++ b/src/resources/compute/ComputeResource.ts @@ -2,12 +2,10 @@ // Licensed under the MIT License. import { BaseResource } from '../BaseResource.js'; -import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; import type { ReadResourceResult, - ServerNotification, - ServerRequest -} from '@modelcontextprotocol/sdk/types.js'; + ServerContext +} from '@modelcontextprotocol/server'; import { resolveComputeRef } from '../../utils/computeRef.js'; /** @@ -29,7 +27,7 @@ export class ComputeResource extends BaseResource { async read( uri: string, // eslint-disable-next-line @typescript-eslint/no-unused-vars - _extra?: RequestHandlerExtra + _extra?: ServerContext ): Promise { const payload = resolveComputeRef(uri); diff --git a/src/resources/inline-payload/InlinePayloadResource.ts b/src/resources/inline-payload/InlinePayloadResource.ts index f8b626d9..d4e3b744 100644 --- a/src/resources/inline-payload/InlinePayloadResource.ts +++ b/src/resources/inline-payload/InlinePayloadResource.ts @@ -2,12 +2,10 @@ // Licensed under the MIT License. import { BaseResource } from '../BaseResource.js'; -import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; import type { ReadResourceResult, - ServerNotification, - ServerRequest -} from '@modelcontextprotocol/sdk/types.js'; + ServerContext +} from '@modelcontextprotocol/server'; import { resolveInlinePayloadRef } from '../../utils/inlinePayloadRef.js'; /** @@ -26,7 +24,7 @@ export class InlinePayloadResource extends BaseResource { async read( uri: string, // eslint-disable-next-line @typescript-eslint/no-unused-vars - _extra?: RequestHandlerExtra + _extra?: ServerContext ): Promise { const payload = resolveInlinePayloadRef(uri); diff --git a/src/resources/place-photo/PlacePhotoResource.ts b/src/resources/place-photo/PlacePhotoResource.ts index 302152f1..1cd45ace 100644 --- a/src/resources/place-photo/PlacePhotoResource.ts +++ b/src/resources/place-photo/PlacePhotoResource.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { lookup } from 'node:dns/promises'; -import type { ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'; +import type { ReadResourceResult } from '@modelcontextprotocol/server'; import { MapboxApiBasedResource } from '../MapboxApiBasedResource.js'; import type { HttpRequest } from '../../utils/types.js'; import { diff --git a/src/resources/temporary/TemporaryDataResource.ts b/src/resources/temporary/TemporaryDataResource.ts index cb2a3e4f..a5613b25 100644 --- a/src/resources/temporary/TemporaryDataResource.ts +++ b/src/resources/temporary/TemporaryDataResource.ts @@ -2,12 +2,10 @@ // Licensed under the MIT License. import { BaseResource } from '../BaseResource.js'; -import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; import type { ReadResourceResult, - ServerNotification, - ServerRequest -} from '@modelcontextprotocol/sdk/types.js'; + ServerContext +} from '@modelcontextprotocol/server'; import { temporaryResourceManager } from '../../utils/temporaryResourceManager.js'; import { getOwnerKeyFromToken, ownerKeyMatches } from '../../utils/jwtUtils.js'; @@ -22,10 +20,7 @@ export class TemporaryDataResource extends BaseResource { 'Temporary storage for large tool responses. Data expires after TTL.'; readonly mimeType = 'application/json'; - async read( - uri: string, - extra?: RequestHandlerExtra - ): Promise { + async read(uri: string, extra?: ServerContext): Promise { const notFound: ReadResourceResult = { contents: [ { @@ -68,7 +63,7 @@ export class TemporaryDataResource extends BaseResource { // shared MAPBOX_ACCESS_TOKEN in a multi-tenant deployment or all env-less // reads would resolve to that one token. const requesterToken = - (extra?.authInfo?.token as string | undefined) ?? + (extra?.http?.authInfo?.token as string | undefined) ?? process.env.MAPBOX_ACCESS_TOKEN; if ( !ownerKeyMatches(resource.owner, getOwnerKeyFromToken(requesterToken)) diff --git a/src/resources/tool-response/ToolResponseResource.ts b/src/resources/tool-response/ToolResponseResource.ts index c9b7bb4d..d95fd043 100644 --- a/src/resources/tool-response/ToolResponseResource.ts +++ b/src/resources/tool-response/ToolResponseResource.ts @@ -1,7 +1,7 @@ // Copyright (c) Mapbox, Inc. // Licensed under the MIT License. -import type { ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'; +import type { ReadResourceResult } from '@modelcontextprotocol/server'; import { MapboxApiBasedResource } from '../MapboxApiBasedResource.js'; import type { HttpRequest } from '../../utils/types.js'; import { resolveResponseRef } from '../../utils/responseRef.js'; diff --git a/src/resources/ui-apps/MapAppUIResource.ts b/src/resources/ui-apps/MapAppUIResource.ts index 152f989f..cc20d8fb 100644 --- a/src/resources/ui-apps/MapAppUIResource.ts +++ b/src/resources/ui-apps/MapAppUIResource.ts @@ -1,12 +1,10 @@ // Copyright (c) Mapbox, Inc. // Licensed under the MIT License. -import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; import type { ReadResourceResult, - ServerNotification, - ServerRequest -} from '@modelcontextprotocol/sdk/types.js'; + ServerContext +} from '@modelcontextprotocol/server'; import { RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server'; import { BaseResource } from '../BaseResource.js'; import type { HttpRequest } from '../../utils/types.js'; @@ -44,12 +42,9 @@ export class MapAppUIResource extends BaseResource { (() => process.env.MAPBOX_API_ENDPOINT || 'https://api.mapbox.com/'); } - async read( - _uri: string, - extra?: RequestHandlerExtra - ): Promise { + async read(_uri: string, extra?: ServerContext): Promise { const accessToken = - (extra?.authInfo?.token as string | undefined) || + (extra?.http?.authInfo?.token as string | undefined) || process.env.MAPBOX_ACCESS_TOKEN || ''; diff --git a/src/resources/version/VersionResource.ts b/src/resources/version/VersionResource.ts index d7645dec..2aabb7ce 100644 --- a/src/resources/version/VersionResource.ts +++ b/src/resources/version/VersionResource.ts @@ -3,7 +3,7 @@ import { BaseResource } from '../BaseResource.js'; import { getVersionInfo } from '../../utils/versionUtils.js'; -import type { ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'; +import type { ReadResourceResult } from '@modelcontextprotocol/server'; /** * Resource exposing MCP server version information. diff --git a/src/tools/BaseTool.ts b/src/tools/BaseTool.ts index eb771efd..d8dc0475 100644 --- a/src/tools/BaseTool.ts +++ b/src/tools/BaseTool.ts @@ -3,14 +3,13 @@ import type { McpServer, - RegisteredTool -} from '@modelcontextprotocol/sdk/server/mcp.js'; -import type { + RegisteredTool, ToolAnnotations, - CallToolResult -} from '@modelcontextprotocol/sdk/types.js'; -import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; + CallToolResult, + ServerContext +} from '@modelcontextprotocol/server'; import { AsyncLocalStorage } from 'node:async_hooks'; +import { lenientOutputSchema } from './lenientOutputSchema.js'; import type { ZodTypeAny } from 'zod'; import type { z } from 'zod'; @@ -101,10 +100,10 @@ export abstract class BaseTool< }; // Add outputSchema if provided — pass the full Zod schema (not just .shape) - // so that .passthrough() and other schema-level settings are preserved when - // the MCP SDK converts it for structured-content validation. + // so that .passthrough() and other schema-level settings are preserved in + // tools/list. Validation only warns; see lenientOutputSchema.ts. if (this.outputSchema) { - config.outputSchema = this.outputSchema; + config.outputSchema = lenientOutputSchema(this.outputSchema, this.name); } // Add _meta for MCP Apps support if provided (includes CSP configuration) @@ -116,8 +115,27 @@ export abstract class BaseTool< this.name, config, // eslint-disable-next-line @typescript-eslint/no-explicit-any - (args: any, extra: any) => - this.invocationServer.run(server, () => this.run(args, extra)) + async (args: any, ctx: ServerContext) => { + const result = await this.invocationServer.run(server, () => + this.run(args, ctx) + ); + // McpServer fails a call to a tool with an output schema that returns + // no structuredContent, before validation can let it through. Some + // tools fall back to text only (validateAndSanitizeOutput drops + // structuredContent that doesn't match), so send an empty object + // rather than turn a usable text answer into an error. + if ( + this.outputSchema && + !result.isError && + result.structuredContent === undefined + ) { + console.warn( + `Output validation warning: tool ${this.name} has an output schema but returned no structured content` + ); + return { ...result, structuredContent: {} }; + } + return result; + } ); } @@ -126,8 +144,7 @@ export abstract class BaseTool< */ abstract run( rawInput: unknown, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - extra?: RequestHandlerExtra + extra?: ServerContext ): Promise; /** diff --git a/src/tools/MapboxApiBasedTool.ts b/src/tools/MapboxApiBasedTool.ts index 4febfcc0..cfe8f769 100644 --- a/src/tools/MapboxApiBasedTool.ts +++ b/src/tools/MapboxApiBasedTool.ts @@ -1,13 +1,13 @@ // Copyright (c) Mapbox, Inc. // Licensed under the MIT License. -import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; -import type { ZodTypeAny } from 'zod'; -import { BaseTool } from './BaseTool.js'; import type { CallToolResult, - ToolAnnotations -} from '@modelcontextprotocol/sdk/types.js'; + ToolAnnotations, + ServerContext +} from '@modelcontextprotocol/server'; +import type { ZodTypeAny } from 'zod'; +import { BaseTool } from './BaseTool.js'; import type { HttpRequest } from '../utils/types.js'; import { context, trace, SpanStatusCode } from '@opentelemetry/api'; import type { ToolExecutionContext } from '../utils/tracing.js'; @@ -80,14 +80,14 @@ export abstract class MapboxApiBasedTool< */ async run( rawInput: unknown, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - extra?: RequestHandlerExtra + + extra?: ServerContext ): Promise { // First check if token is provided via authentication context // Check both standard token field and accessToken in extra for compatibility // In the streamableHttp, the authInfo is injected into extra from `req.auth` // https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/streamableHttp.ts#L405 - const authToken = extra?.authInfo?.token; + const authToken = extra?.http?.authInfo?.token; const accessToken = authToken || MapboxApiBasedTool.mapboxAccessToken; if (!accessToken) { const errorMessage = diff --git a/src/tools/area-tool/AreaTool.ts b/src/tools/area-tool/AreaTool.ts index 1f57fb73..9306659a 100644 --- a/src/tools/area-tool/AreaTool.ts +++ b/src/tools/area-tool/AreaTool.ts @@ -7,7 +7,7 @@ import { createLocalToolExecutionContext } from '../../utils/tracing.js'; import { BaseTool } from '../BaseTool.js'; import { AreaInputSchema } from './AreaTool.input.schema.js'; import { AreaOutputSchema, type AreaOutput } from './AreaTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; /** * AreaTool - Calculate the area of a polygon or multipolygon diff --git a/src/tools/bearing-tool/BearingTool.ts b/src/tools/bearing-tool/BearingTool.ts index 10269b58..0acc7fd8 100644 --- a/src/tools/bearing-tool/BearingTool.ts +++ b/src/tools/bearing-tool/BearingTool.ts @@ -10,7 +10,7 @@ import { BearingOutputSchema, type BearingOutput } from './BearingTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; /** * BearingTool - Calculate the bearing between two geographic coordinates diff --git a/src/tools/bounding-box-tool/BoundingBoxTool.ts b/src/tools/bounding-box-tool/BoundingBoxTool.ts index 601c4452..addabe74 100644 --- a/src/tools/bounding-box-tool/BoundingBoxTool.ts +++ b/src/tools/bounding-box-tool/BoundingBoxTool.ts @@ -16,7 +16,7 @@ import { BoundingBoxOutputSchema, type BoundingBoxOutput } from './BoundingBoxTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; /** * BoundingBoxTool - Calculate the bounding box of a geometry diff --git a/src/tools/buffer-tool/BufferTool.ts b/src/tools/buffer-tool/BufferTool.ts index a566c785..5c7f34e4 100644 --- a/src/tools/buffer-tool/BufferTool.ts +++ b/src/tools/buffer-tool/BufferTool.ts @@ -10,7 +10,7 @@ import { BufferOutputSchema, type BufferOutput } from './BufferTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; /** * BufferTool - Create a buffer zone around a geometry diff --git a/src/tools/category-list-tool/CategoryListTool.ts b/src/tools/category-list-tool/CategoryListTool.ts index d97c4283..9370b0b6 100644 --- a/src/tools/category-list-tool/CategoryListTool.ts +++ b/src/tools/category-list-tool/CategoryListTool.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import type { HttpRequest } from '../../utils/types.js'; import type { CategoryListInput } from './CategoryListTool.input.schema.js'; import { CategoryListInputSchema } from './CategoryListTool.input.schema.js'; diff --git a/src/tools/category-search-tool/CategorySearchTool.ts b/src/tools/category-search-tool/CategorySearchTool.ts index 51728465..80978652 100644 --- a/src/tools/category-search-tool/CategorySearchTool.ts +++ b/src/tools/category-search-tool/CategorySearchTool.ts @@ -3,7 +3,7 @@ import type { z } from 'zod'; import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import type { HttpRequest } from '../../utils/types.js'; import { CategorySearchInputSchema } from './CategorySearchTool.input.schema.js'; import { CategorySearchResponseSchema } from './CategorySearchTool.output.schema.js'; diff --git a/src/tools/centroid-tool/CentroidTool.ts b/src/tools/centroid-tool/CentroidTool.ts index 72b9cdb3..8893d707 100644 --- a/src/tools/centroid-tool/CentroidTool.ts +++ b/src/tools/centroid-tool/CentroidTool.ts @@ -10,7 +10,7 @@ import { CentroidOutputSchema, type CentroidOutput } from './CentroidTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; /** * CentroidTool - Calculate the center point (centroid) of a polygon or multipolygon diff --git a/src/tools/convex-tool/ConvexTool.ts b/src/tools/convex-tool/ConvexTool.ts index 1fea5009..104f8468 100644 --- a/src/tools/convex-tool/ConvexTool.ts +++ b/src/tools/convex-tool/ConvexTool.ts @@ -10,7 +10,7 @@ import { ConvexOutputSchema, type ConvexOutput } from './ConvexTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; export class ConvexTool extends BaseTool< typeof ConvexInputSchema, diff --git a/src/tools/destination-tool/DestinationTool.ts b/src/tools/destination-tool/DestinationTool.ts index ae8f4a98..6520429e 100644 --- a/src/tools/destination-tool/DestinationTool.ts +++ b/src/tools/destination-tool/DestinationTool.ts @@ -10,7 +10,7 @@ import { DestinationOutputSchema, type DestinationOutput } from './DestinationTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; export class DestinationTool extends BaseTool< typeof DestinationInputSchema, diff --git a/src/tools/difference-tool/DifferenceTool.ts b/src/tools/difference-tool/DifferenceTool.ts index 6304f773..de463fcb 100644 --- a/src/tools/difference-tool/DifferenceTool.ts +++ b/src/tools/difference-tool/DifferenceTool.ts @@ -10,7 +10,7 @@ import { DifferenceOutputSchema, type DifferenceOutput } from './DifferenceTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import { renderHint } from '../../utils/storeMapPayload.js'; import { buildComputeRef } from '../../utils/computeRef.js'; diff --git a/src/tools/directions-tool/DirectionsTool.ts b/src/tools/directions-tool/DirectionsTool.ts index 45cb59d8..e0b40bd1 100644 --- a/src/tools/directions-tool/DirectionsTool.ts +++ b/src/tools/directions-tool/DirectionsTool.ts @@ -3,7 +3,7 @@ import type { z } from 'zod'; import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import { cleanResponseData } from './cleanResponseData.js'; import { buildDirectionsRequestUrl } from './buildDirectionsRequestUrl.js'; import { DirectionsInputSchema } from './DirectionsTool.input.schema.js'; diff --git a/src/tools/distance-tool/DistanceTool.ts b/src/tools/distance-tool/DistanceTool.ts index 10ec8b4c..44b3a486 100644 --- a/src/tools/distance-tool/DistanceTool.ts +++ b/src/tools/distance-tool/DistanceTool.ts @@ -10,7 +10,7 @@ import { DistanceOutputSchema, type DistanceOutput } from './DistanceTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; /** * DistanceTool - Calculate distance between two geographic coordinates diff --git a/src/tools/feedback-tool/FeedbackTool.ts b/src/tools/feedback-tool/FeedbackTool.ts index 97ad13d1..ebb72199 100644 --- a/src/tools/feedback-tool/FeedbackTool.ts +++ b/src/tools/feedback-tool/FeedbackTool.ts @@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto'; import { distance as turfDistance } from '@turf/turf'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; import { FeedbackInputSchema, diff --git a/src/tools/ground-location-tool/GroundLocationTool.ts b/src/tools/ground-location-tool/GroundLocationTool.ts index 29aa4b0c..5d5c7ca4 100644 --- a/src/tools/ground-location-tool/GroundLocationTool.ts +++ b/src/tools/ground-location-tool/GroundLocationTool.ts @@ -3,7 +3,7 @@ import type { z } from 'zod'; import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import type { HttpRequest } from '../../utils/types.js'; import { GroundLocationInputSchema } from './GroundLocationTool.input.schema.js'; import { diff --git a/src/tools/intersect-tool/IntersectTool.ts b/src/tools/intersect-tool/IntersectTool.ts index 811b2b30..323a43f8 100644 --- a/src/tools/intersect-tool/IntersectTool.ts +++ b/src/tools/intersect-tool/IntersectTool.ts @@ -10,7 +10,7 @@ import { IntersectOutputSchema, type IntersectOutput } from './IntersectTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import { renderHint } from '../../utils/storeMapPayload.js'; import { buildComputeRef } from '../../utils/computeRef.js'; diff --git a/src/tools/isochrone-tool/IsochroneTool.ts b/src/tools/isochrone-tool/IsochroneTool.ts index e30e894d..d93bc935 100644 --- a/src/tools/isochrone-tool/IsochroneTool.ts +++ b/src/tools/isochrone-tool/IsochroneTool.ts @@ -3,7 +3,7 @@ import type { z } from 'zod'; import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import type { HttpRequest } from '../../utils/types.js'; import { IsochroneInputSchema } from './IsochroneTool.input.schema.js'; import { buildIsochroneRequestUrl } from './buildIsochroneRequestUrl.js'; diff --git a/src/tools/length-tool/LengthTool.ts b/src/tools/length-tool/LengthTool.ts index b2480182..0ac081b3 100644 --- a/src/tools/length-tool/LengthTool.ts +++ b/src/tools/length-tool/LengthTool.ts @@ -10,7 +10,7 @@ import { LengthOutputSchema, type LengthOutput } from './LengthTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; export class LengthTool extends BaseTool< typeof LengthInputSchema, diff --git a/src/tools/lenientOutputSchema.ts b/src/tools/lenientOutputSchema.ts new file mode 100644 index 00000000..c81b8bbd --- /dev/null +++ b/src/tools/lenientOutputSchema.ts @@ -0,0 +1,41 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import type { StandardSchemaWithJSON } from '@modelcontextprotocol/server'; +import type { ZodTypeAny } from 'zod'; + +/** + * Wraps a tool's output schema so McpServer still publishes it in + * `tools/list` but doesn't fail the call when `structuredContent` doesn't + * match it. + * + * McpServer turns an output-schema mismatch into an error result, which + * throws away a response the model could have used because an upstream API + * added or changed a field. SDK v1 needed a patch to the SDK for this (see + * patches/README.md); v2 takes any Standard Schema, so this keeps the + * schema's JSON Schema and swaps in a validator that logs the mismatch to + * stderr and passes the data through unchanged. + */ +export function lenientOutputSchema( + schema: ZodTypeAny, + toolName: string +): StandardSchemaWithJSON { + const standard = (schema as unknown as StandardSchemaWithJSON)['~standard']; + return { + '~standard': { + version: 1, + vendor: standard.vendor, + jsonSchema: standard.jsonSchema, + validate: async (value: unknown) => { + const result = await standard.validate(value); + if (result.issues?.length) { + console.warn( + `Output validation warning: structured content for tool ${toolName} doesn't match its output schema: ` + + result.issues.map((issue) => issue.message).join(', ') + ); + } + return { value }; + } + } + } as StandardSchemaWithJSON; +} diff --git a/src/tools/map-matching-tool/MapMatchingTool.ts b/src/tools/map-matching-tool/MapMatchingTool.ts index 656a71d3..089180f8 100644 --- a/src/tools/map-matching-tool/MapMatchingTool.ts +++ b/src/tools/map-matching-tool/MapMatchingTool.ts @@ -3,7 +3,7 @@ import type { z } from 'zod'; import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import { MapMatchingInputSchema } from './MapMatchingTool.input.schema.js'; import { buildMapMatchingRequestUrl } from './buildMapMatchingRequestUrl.js'; import { diff --git a/src/tools/matrix-tool/MatrixTool.ts b/src/tools/matrix-tool/MatrixTool.ts index ba993fdc..56d65400 100644 --- a/src/tools/matrix-tool/MatrixTool.ts +++ b/src/tools/matrix-tool/MatrixTool.ts @@ -4,7 +4,7 @@ import type { z } from 'zod'; import { URLSearchParams } from 'node:url'; import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import type { HttpRequest } from '../../utils/types.js'; import { MatrixInputSchema } from './MatrixTool.input.schema.js'; import { diff --git a/src/tools/midpoint-tool/MidpointTool.ts b/src/tools/midpoint-tool/MidpointTool.ts index d29f5803..c48df664 100644 --- a/src/tools/midpoint-tool/MidpointTool.ts +++ b/src/tools/midpoint-tool/MidpointTool.ts @@ -10,7 +10,7 @@ import { MidpointOutputSchema, type MidpointOutput } from './MidpointTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; /** * MidpointTool - Calculate the geographic midpoint between two coordinates diff --git a/src/tools/nearest-point-on-line-tool/NearestPointOnLineTool.ts b/src/tools/nearest-point-on-line-tool/NearestPointOnLineTool.ts index 4ce44135..82e750c1 100644 --- a/src/tools/nearest-point-on-line-tool/NearestPointOnLineTool.ts +++ b/src/tools/nearest-point-on-line-tool/NearestPointOnLineTool.ts @@ -10,7 +10,7 @@ import { NearestPointOnLineOutputSchema, type NearestPointOnLineOutput } from './NearestPointOnLineTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; export class NearestPointOnLineTool extends BaseTool< typeof NearestPointOnLineInputSchema, diff --git a/src/tools/nearest-point-tool/NearestPointTool.ts b/src/tools/nearest-point-tool/NearestPointTool.ts index b5aa725a..2c73310f 100644 --- a/src/tools/nearest-point-tool/NearestPointTool.ts +++ b/src/tools/nearest-point-tool/NearestPointTool.ts @@ -10,7 +10,7 @@ import { NearestPointOutputSchema, type NearestPointOutput } from './NearestPointTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; export class NearestPointTool extends BaseTool< typeof NearestPointInputSchema, diff --git a/src/tools/optimization-tool/OptimizationTool.ts b/src/tools/optimization-tool/OptimizationTool.ts index 6c799b25..e717c12f 100644 --- a/src/tools/optimization-tool/OptimizationTool.ts +++ b/src/tools/optimization-tool/OptimizationTool.ts @@ -11,7 +11,7 @@ import { OptimizationOutputSchema, type OptimizationOutput } from './OptimizationTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import type { ToolExecutionContext } from '../../utils/tracing.js'; import type { HttpRequest } from '../../utils/types.js'; import { buildOptimizationRequestUrl } from './buildOptimizationRequestUrl.js'; diff --git a/src/tools/optimization-tool/OptimizationV2Tool.input.schema.ts b/src/tools/optimization-tool/OptimizationV2Tool.input.schema.ts deleted file mode 100644 index bae11412..00000000 --- a/src/tools/optimization-tool/OptimizationV2Tool.input.schema.ts +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (c) Mapbox, Inc. -// Licensed under the MIT License. - -import z from 'zod'; -import { coordinateSchema } from '../../schemas/shared.js'; - -// Profile schema (driving, walking, cycling, driving-traffic) -const profileSchema = z - .enum([ - 'mapbox/driving', - 'mapbox/walking', - 'mapbox/cycling', - 'mapbox/driving-traffic' - ]) - .describe('Routing profile'); - -/** - * Input schema for OptimizationV2Tool (V2 API - Beta) - * - * The V2 API is currently in beta and requires early access. - * Supports advanced features: time windows, capacity limits, vehicle capabilities, - * shipments, breaks, and more. - */ - -const optionsSchema = z.object({ - objectives: z - .array( - z.enum(['min-total-travel-duration', 'min-schedule-completion-time']) - ) - .optional() - .describe('Optimization objectives') -}); - -const iso8601Schema = z - .string() - .regex( - /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?([+-]\d{2}:\d{2}|Z)?$/, - 'Must be a valid ISO 8601 datetime string (e.g., 2023-07-22T14:29:23-07:00)' - ); - -const timeWindowSchema = z.object({ - earliest: iso8601Schema.describe('Earliest time for this window'), - latest: iso8601Schema.describe('Latest time for this window'), - type: z - .enum(['strict', 'soft', 'soft_start', 'soft_end']) - .optional() - .describe('Type of time window constraint') -}); - -const vehicleBreakSchema = z.object({ - earliest_start: iso8601Schema.describe('Earliest start time for break'), - latest_end: iso8601Schema.describe('Latest end time for break'), - duration: z.number().int().positive().describe('Break duration in seconds') -}); - -const vehicleSchema = z.object({ - name: z.string().describe('Unique identifier for this vehicle'), - routing_profile: profileSchema - .optional() - .describe('Routing profile for this vehicle'), - start_location: z - .string() - .optional() - .describe( - 'Starting location as a STRING name (e.g., "location-0" for coordinates[0], NOT the integer 0)' - ), - end_location: z - .string() - .optional() - .describe( - 'Ending location as a STRING name (e.g., "location-0" for coordinates[0], NOT the integer 0)' - ), - capacities: z - .record(z.string(), z.number()) - .optional() - .describe('Custom capacity dimensions (e.g., weight, volume)'), - capabilities: z - .array(z.string()) - .optional() - .describe('Required vehicle capabilities'), - earliest_start: iso8601Schema - .optional() - .describe('Earliest operation start time'), - latest_end: iso8601Schema.optional().describe('Latest operation end time'), - breaks: z - .array(vehicleBreakSchema) - .optional() - .describe('Vehicle break requirements'), - loading_policy: z - .enum(['any', 'fifo', 'lifo']) - .optional() - .describe('Loading/unloading policy') -}); - -const serviceSchema = z.object({ - name: z.string().describe('Unique identifier for this service'), - location: z - .string() - .describe( - 'Location as a STRING name where service is performed. Use "location-0" for coordinates[0], "location-1" for coordinates[1], etc. (NOT integers like 0, 1, 2)' - ), - duration: z - .number() - .int() - .nonnegative() - .optional() - .default(0) - .describe('Service duration in seconds'), - requirements: z - .array(z.string()) - .optional() - .describe('Required vehicle capabilities'), - service_times: z - .array(timeWindowSchema) - .optional() - .describe('Time window constraints for service') -}); - -const shipmentSchema = z.object({ - name: z.string().describe('Unique identifier for this shipment'), - from: z - .string() - .describe( - 'Pickup location as a STRING name (e.g., "location-0" for coordinates[0], NOT the integer 0)' - ), - to: z - .string() - .describe( - 'Delivery location as a STRING name (e.g., "location-0" for coordinates[0], NOT the integer 0)' - ), - size: z - .record(z.string(), z.number()) - .optional() - .describe('Capacity consumption (weight, volume, etc.)'), - requirements: z - .array(z.string()) - .optional() - .describe('Required vehicle capabilities'), - pickup_duration: z - .number() - .int() - .nonnegative() - .optional() - .default(0) - .describe('Pickup duration in seconds'), - dropoff_duration: z - .number() - .int() - .nonnegative() - .optional() - .default(0) - .describe('Dropoff duration in seconds'), - pickup_times: z - .array(timeWindowSchema) - .optional() - .describe('Time window constraints for pickup'), - dropoff_times: z - .array(timeWindowSchema) - .optional() - .describe('Time window constraints for dropoff') -}); - -export const OptimizationV2InputSchema = z.object({ - coordinates: z - .array(coordinateSchema) - .min(2, 'At least 2 coordinates are required') - .max(1000, 'Maximum 1000 coordinates allowed') - .describe( - 'Array of {longitude, latitude} coordinate pairs to optimize a route through. ' + - 'Must include at least 2 coordinate pairs. ' + - 'Up to 1000 coordinates total are supported.' - ), - profile: profileSchema - .optional() - .default('mapbox/driving') - .describe('Routing profile to use for optimization'), - vehicles: z - .array(vehicleSchema) - .optional() - .describe( - 'Array of vehicle objects. If not provided, a default vehicle will be created automatically. ' + - 'IMPORTANT: If you provide vehicles, you MUST also provide either "services" or "shipments" to define the stops each vehicle should visit.' - ), - services: z - .array(serviceSchema) - .optional() - .describe( - 'Array of service objects representing individual stops/tasks (e.g., deliveries, pickups, visits). ' + - 'Each service is performed at a single location. Required when using custom vehicles.' - ), - shipments: z - .array(shipmentSchema) - .optional() - .describe( - 'Array of shipment objects representing paired pickup/delivery tasks. ' + - 'Each shipment has a pickup location and a delivery location. Can be used with or instead of services when using custom vehicles.' - ), - options: optionsSchema.optional().describe('Optimization options'), - max_polling_attempts: z - .number() - .int() - .positive() - .optional() - .default(30) - .describe('Maximum number of polling attempts (default: 30)'), - polling_interval_ms: z - .number() - .int() - .positive() - .optional() - .default(1000) - .describe('Polling interval in milliseconds (default: 1000)') -}); - -export type OptimizationV2Input = z.infer; diff --git a/src/tools/optimization-tool/OptimizationV2Tool.output.schema.ts b/src/tools/optimization-tool/OptimizationV2Tool.output.schema.ts deleted file mode 100644 index 2c711cf6..00000000 --- a/src/tools/optimization-tool/OptimizationV2Tool.output.schema.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Mapbox, Inc. -// Licensed under the MIT License. - -import { z } from 'zod'; - -/** - * Stop schema for route stops - * Made flexible to handle various API response formats - */ -const stopSchema = z - .object({ - type: z - .string() - .describe( - 'Type of stop. Known values include "start" (vehicle begins), "service" (perform service), "pickup"/"dropoff" (shipment handling), "break" (vehicle break), "end" (vehicle finishes). Additional values may be returned as the API evolves.' - ), - location: z - .string() - .describe( - 'Location name for this stop (e.g., "location-0", "location-1")' - ), - eta: z - .string() - .optional() - .describe( - 'Estimated time of arrival in ISO 8601 format (e.g., "2023-07-22T14:29:23-07:00")' - ), - odometer: z - .number() - .optional() - .describe('Total distance traveled to reach this stop in meters'), - wait: z - .number() - .optional() - .describe('Wait time at this stop in seconds before proceeding'), - duration: z - .number() - .optional() - .describe('Duration of service/activity at this stop in seconds'), - services: z - .array(z.string()) - .optional() - .describe('Array of service names fulfilled at this stop'), - pickups: z - .array(z.string()) - .optional() - .describe('Array of shipment names picked up at this stop'), - dropoffs: z - .array(z.string()) - .optional() - .describe('Array of shipment names dropped off at this stop') - }) - .passthrough(); // Allow additional fields from API - -/** - * Route schema - */ -const routeSchema = z - .object({ - vehicle: z - .string() - .describe( - 'Vehicle name for this route (matches the vehicle name from input)' - ), - stops: z - .array(stopSchema) - .describe( - 'Ordered sequence of stops for this vehicle, from start to end. Includes all services, pickups, dropoffs, and breaks in optimized order.' - ) - }) - .passthrough(); // Allow additional fields from API - -/** - * Dropped items schema - */ -const droppedSchema = z - .object({ - services: z - .array(z.string()) - .optional() - .describe( - 'Array of service names that could not be fulfilled due to constraints (e.g., time windows, capacity, capabilities)' - ), - shipments: z - .array(z.string()) - .optional() - .describe( - 'Array of shipment names that could not be fulfilled due to constraints (e.g., time windows, capacity, capabilities)' - ) - }) - .passthrough(); // Allow additional fields from API - -/** - * Output schema for OptimizationV2Tool (V2 API - Beta) - * Uses passthrough to be flexible with API response variations - */ -export const OptimizationV2OutputSchema = z - .object({ - version: z - .number() - .optional() - .describe('API version number (always 1 for Optimization API v2)'), - dropped: droppedSchema - .optional() - .describe( - 'Items that could not be fulfilled in the optimization. If present, contains arrays of service/shipment names that were dropped.' - ), - routes: z - .array(routeSchema) - .optional() - .describe( - 'Array of optimized routes, one per vehicle. Each route contains the vehicle name and an ordered sequence of stops. May be undefined if optimization fails completely.' - ), - // Error response fields from API - code: z - .string() - .optional() - .describe( - 'Error code from API (e.g., "internal_error"). Present only when the API returns an error response.' - ), - message: z - .string() - .optional() - .describe( - 'Human-readable error message from API. Present only when the API returns an error response.' - ), - ref: z - .string() - .optional() - .describe( - 'Reference ID for the error from API. Present only when the API returns an error response.' - ) - }) - .passthrough(); // Allow additional fields from API - -/** - * Type inference for OptimizationV2Output - */ -export type OptimizationV2Output = z.infer; diff --git a/src/tools/optimization-tool/OptimizationV2Tool.ts b/src/tools/optimization-tool/OptimizationV2Tool.ts deleted file mode 100644 index 643563b2..00000000 --- a/src/tools/optimization-tool/OptimizationV2Tool.ts +++ /dev/null @@ -1,442 +0,0 @@ -// Copyright (c) Mapbox, Inc. -// Licensed under the MIT License. - -import type { z } from 'zod'; -import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import type { HttpRequest } from '../../utils/types.js'; -import type { - TaskStore, - CreateTaskRequestHandlerExtra, - TaskRequestHandlerExtra -} from '@modelcontextprotocol/sdk/experimental/tasks/interfaces.js'; -import { OptimizationV2InputSchema } from './OptimizationV2Tool.input.schema.js'; -import { - OptimizationV2OutputSchema, - type OptimizationV2Output -} from './OptimizationV2Tool.output.schema.js'; - -/** - * OptimizationV2Tool - Advanced route optimization using Mapbox Optimization API V2 (BETA) - * - * ⚠️ IMPORTANT: This tool uses the V2 API which is currently in beta and requires early access. - * The tool is not registered by default. To enable it, add it to the toolRegistry. - * - * V2 Features: - * - Time window constraints for services and shipments - * - Vehicle capacity limits for multiple resource types - * - Driver shifts with earliest start and latest end times - * - Pickup and dropoff constraints for shipment handling - * - Vehicle capabilities requirements (e.g., refrigeration, ladder) - * - Loading policies (FIFO, LIFO, or any order) - * - Mandated breaks for vehicles - * - * API documentation: https://docs.mapbox.com/api/navigation/optimization/ - */ - -/** - * Helper function to validate JWT format - */ -function isValidJwtFormat(token: string): boolean { - const parts = token.split('.'); - if (parts.length !== 3) return false; - return parts.every((part) => part.length > 0); -} - -/** - * Helper function to get Mapbox API endpoint - */ -function getMapboxApiEndpoint(): string { - return process.env.MAPBOX_API_ENDPOINT || 'https://api.mapbox.com/'; -} - -/** - * Core function to submit optimization job to Mapbox API - */ -async function submitOptimizationJob( - input: z.infer, - accessToken: string, - httpRequest: HttpRequest -): Promise<{ jobId: string; status: string }> { - // Validate: If vehicles are provided, at least one service or shipment is required - if (input.vehicles && input.vehicles.length > 0) { - const hasServices = input.services && input.services.length > 0; - const hasShipments = input.shipments && input.shipments.length > 0; - - if (!hasServices && !hasShipments) { - throw new Error( - 'When vehicles are provided, at least one service or shipment is required' - ); - } - } - - // Convert simplified input to Optimization API v2 format - // Create locations array with auto-generated names - const locations = input.coordinates.map((coord, index) => ({ - name: `location-${index}`, - coordinates: [coord.longitude, coord.latitude] as [number, number] - })); - - // If vehicles are not provided, create a default vehicle - let vehicles: Array; - let services: Array; - let shipments: Array | undefined; - - if (!input.vehicles || input.vehicles.length === 0) { - // Simplified mode: auto-generate vehicle and services from coordinates - vehicles = [ - { - name: 'vehicle-1', - routing_profile: input.profile, - start_location: 'location-0', // Start at first location - end_location: 'location-0' // Return to first location - } - ]; - - // Create services for all locations except the first (which is start/end) - services = input.coordinates.slice(1).map((_, index) => ({ - name: `service-${index + 1}`, - location: `location-${index + 1}`, - duration: 0 // No service time by default - })); - } else { - // Advanced mode: use provided vehicles, services, and shipments - vehicles = input.vehicles; - services = input.services || []; - shipments = input.shipments; - } - - // Build request body - const requestBody: { - version: number; - locations: Array<{ name: string; coordinates: [number, number] }>; - vehicles: Array; - services: Array; - shipments?: Array; - } = { - version: 1, - locations, - vehicles, - services - }; - - if (shipments && shipments.length > 0) { - requestBody.shipments = shipments; - } - - // Step 1: POST to create optimization job - const postUrl = `${getMapboxApiEndpoint()}optimized-trips/v2?access_token=${accessToken}`; - - const postResponse = await httpRequest(postUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(requestBody) - }); - - if (!postResponse.ok) { - const errorText = await postResponse.text(); - let errorMessage = `Request failed with status ${postResponse.status}: ${postResponse.statusText}`; - - try { - const errorJson = JSON.parse(errorText); - if (errorJson.message) { - errorMessage = `${errorMessage} - ${errorJson.message}`; - } - } catch { - // If parsing fails, use the raw text - if (errorText) { - errorMessage = `${errorMessage} - ${errorText}`; - } - } - - throw new Error(errorMessage); - } - - const postData = (await postResponse.json()) as { - id: string; - status: string; - }; - - return { - jobId: postData.id, - status: postData.status - }; -} - -/** - * Core function to poll optimization job status - */ -async function pollOptimizationJob( - jobId: string, - accessToken: string, - httpRequest: HttpRequest -): Promise<{ status: number; data?: OptimizationV2Output; error?: string }> { - const getUrl = `${getMapboxApiEndpoint()}optimized-trips/v2/${jobId}?access_token=${accessToken}`; - const getResponse = await httpRequest(getUrl); - - if (!getResponse.ok) { - if (getResponse.status === 404) { - return { - status: 404, - error: `Optimization job ${jobId} not found` - }; - } - - return { - status: getResponse.status, - error: `Request failed with status ${getResponse.status}: ${getResponse.statusText}` - }; - } - - // HTTP 200 means the optimization is complete - if (getResponse.status === 200) { - const result = (await getResponse.json()) as OptimizationV2Output; - return { - status: 200, - data: result - }; - } - - // HTTP 202 means still processing - return { - status: 202 - }; -} - -/** - * Background polling function that runs until job completes - */ -async function backgroundPollOptimizationJob( - jobId: string, - accessToken: string, - httpRequest: HttpRequest, - taskStore: TaskStore, - taskId: string, - maxAttempts: number, - pollingInterval: number -): Promise { - let attempts = 0; - - while (attempts < maxAttempts) { - try { - const pollResult = await pollOptimizationJob( - jobId, - accessToken, - httpRequest - ); - - if (pollResult.status === 200 && pollResult.data) { - // Job completed successfully - try { - const validatedData = OptimizationV2OutputSchema.parse( - pollResult.data - ); - - await taskStore.storeTaskResult(taskId, 'completed', { - content: [ - { type: 'text', text: JSON.stringify(validatedData, null, 2) } - ], - structuredContent: validatedData - }); - } catch { - // If validation fails, return raw result anyway - await taskStore.storeTaskResult(taskId, 'completed', { - content: [ - { type: 'text', text: JSON.stringify(pollResult.data, null, 2) } - ], - structuredContent: pollResult.data - }); - } - return; - } else if (pollResult.error) { - // Job failed or not found - await taskStore.storeTaskResult(taskId, 'failed', { - content: [{ type: 'text', text: pollResult.error }], - isError: true - }); - return; - } - - // Still processing (status 202), update task status - await taskStore.updateTaskStatus( - taskId, - 'working', - `Processing optimization job (attempt ${attempts + 1}/${maxAttempts})` - ); - - // Wait before next poll - await new Promise((resolve) => setTimeout(resolve, pollingInterval)); - attempts++; - } catch (error) { - // Error during polling - await taskStore.storeTaskResult(taskId, 'failed', { - content: [ - { - type: 'text', - text: `Error polling optimization job: ${error instanceof Error ? error.message : String(error)}` - } - ], - isError: true - }); - return; - } - } - - // Timed out - await taskStore.storeTaskResult(taskId, 'failed', { - content: [ - { - type: 'text', - text: `Optimization timed out after ${maxAttempts} attempts. Job ID: ${jobId}. You can check the status later using the job ID.` - } - ], - isError: true - }); -} - -/** - * Register the Optimization V2 tool as a task-based tool - * - * ⚠️ BETA: This tool uses the V2 API which requires early access. - * Not registered by default - call this function manually to enable. - */ -export function registerOptimizationV2Task( - server: McpServer, - httpRequest: HttpRequest -): void { - server.experimental.tasks.registerToolTask( - 'optimization_v2_tool', - { - title: 'Optimization Tool V2 (Beta)', - description: - 'Solves vehicle routing problems using the Mapbox Optimization API v2 (Beta). ' + - 'This is a long-running async task that submits an optimization job and polls for results. ' + - 'Supports up to 1000 coordinates in {longitude, latitude} format. ' + - 'Returns optimized routes with stops, ETAs, and dropped items. ' + - '\n\n' + - 'USAGE MODES:\n' + - '1. SIMPLIFIED MODE (recommended for basic routing): Provide only "coordinates" and optionally "profile". ' + - 'The tool will automatically create a default vehicle that visits all locations in optimized order, ' + - 'starting and ending at the first coordinate.\n' + - '2. ADVANCED MODE (for complex scenarios): If you specify "vehicles", you MUST also provide either ' + - '"services" (single stops like deliveries or pickups) or "shipments" (paired pickup/dropoff tasks). ' + - 'Each service/shipment defines a stop that vehicles must visit.\n' + - '\n' + - 'KEY CONCEPTS:\n' + - '- "services": Individual stops at locations (e.g., deliver package to address A, pick up at coffee shop B)\n' + - '- "shipments": Paired pickup/delivery tasks (e.g., pick up from warehouse, deliver to customer)\n' + - '- "vehicles": Routing agents with optional constraints like time windows, capacities, and capabilities\n' + - '\n' + - 'LOCATION NAMING (CRITICAL):\n' + - 'The tool auto-generates STRING location names from your coordinates array as "location-0", "location-1", "location-2", etc. ' + - 'When creating services, shipments, or vehicles, you MUST reference these auto-generated STRINGS (NOT integers, NOT array indices):\n' + - '- coordinates[0] → use STRING "location-0" (NOT integer 0)\n' + - '\n' + - 'EXAMPLES:\n' + - '✓ CORRECT: service.location = "location-2" (string)\n' + - '✗ WRONG: service.location = 2 (integer)\n' + - '\n' + - 'IMPORTANT: This is an async task. The task will start immediately but results will be available later via task polling.', - inputSchema: OptimizationV2InputSchema, - outputSchema: OptimizationV2OutputSchema, - annotations: { - title: 'Optimization Tool', - readOnlyHint: true, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true - } - }, - { - async createTask(args: unknown, extra: CreateTaskRequestHandlerExtra) { - // Validate input - const input = OptimizationV2InputSchema.parse(args); - - // Get and validate access token (from environment only for now) - const accessToken = - process.env.MAPBOX_ACCESS_TOKEN || - ( - extra as CreateTaskRequestHandlerExtra & { - authInfo?: { token?: string }; - } - ).authInfo?.token; - if (!accessToken) { - throw new Error( - 'No access token available. Please provide via Bearer auth or MAPBOX_ACCESS_TOKEN env var' - ); - } - if (!isValidJwtFormat(accessToken)) { - throw new Error('Access token is not in valid JWT format'); - } - - // Create the task - const task = await extra.taskStore.createTask({ - ttl: 300000 // 5 minutes TTL - }); - - // Submit optimization job to Mapbox API - try { - const { jobId } = await submitOptimizationJob( - input, - accessToken, - httpRequest - ); - - // Start background polling (fire and forget) - const maxAttempts = input.max_polling_attempts ?? 10; - const pollingInterval = input.polling_interval_ms ?? 1000; - - // Don't await this - let it run in the background - void backgroundPollOptimizationJob( - jobId, - accessToken, - httpRequest, - extra.taskStore, - task.taskId, - maxAttempts, - pollingInterval - ); - - // Update task status to show it's working - await extra.taskStore.updateTaskStatus( - task.taskId, - 'working', - `Optimization job submitted: ${jobId}` - ); - - // Return CreateTaskResult with the created task - return { - task - }; - } catch (error) { - // Failed to submit job, mark task as failed - await extra.taskStore.storeTaskResult(task.taskId, 'failed', { - content: [ - { - type: 'text', - text: `Failed to submit optimization job: ${error instanceof Error ? error.message : String(error)}` - } - ], - isError: true - }); - - throw error; - } - }, - - async getTask(_args: unknown, extra: TaskRequestHandlerExtra) { - const task = await extra.taskStore.getTask(extra.taskId); - if (!task) { - throw new Error(`Task ${extra.taskId} not found`); - } - return task; - }, - - async getTaskResult(_args: unknown, extra: TaskRequestHandlerExtra) { - const result = await extra.taskStore.getTaskResult(extra.taskId); - // taskStore.getTaskResult returns a Result type, but we know we stored a CallToolResult - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return result as any; - } - } - ); -} diff --git a/src/tools/place-details-tool/PlaceDetailsTool.ts b/src/tools/place-details-tool/PlaceDetailsTool.ts index 8d6d3af2..5226d6b6 100644 --- a/src/tools/place-details-tool/PlaceDetailsTool.ts +++ b/src/tools/place-details-tool/PlaceDetailsTool.ts @@ -3,7 +3,7 @@ import type { z } from 'zod'; import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import type { HttpRequest } from '../../utils/types.js'; import type { ToolExecutionContext } from '../../utils/tracing.js'; import { PlaceDetailsInputSchema } from './PlaceDetailsTool.input.schema.js'; diff --git a/src/tools/points-within-polygon-tool/PointsWithinPolygonTool.ts b/src/tools/points-within-polygon-tool/PointsWithinPolygonTool.ts index e9452cfd..9aac0308 100644 --- a/src/tools/points-within-polygon-tool/PointsWithinPolygonTool.ts +++ b/src/tools/points-within-polygon-tool/PointsWithinPolygonTool.ts @@ -15,7 +15,7 @@ import { PointsWithinPolygonOutputSchema, type PointsWithinPolygonOutput } from './PointsWithinPolygonTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; export class PointsWithinPolygonTool extends BaseTool< typeof PointsWithinPolygonInputSchema, diff --git a/src/tools/render-map-tool/RenderMapTool.ts b/src/tools/render-map-tool/RenderMapTool.ts index e3a5eebe..16500299 100644 --- a/src/tools/render-map-tool/RenderMapTool.ts +++ b/src/tools/render-map-tool/RenderMapTool.ts @@ -2,14 +2,16 @@ // Licensed under the MIT License. import { context, SpanStatusCode, trace } from '@opentelemetry/api'; -import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; +import type { + CallToolResult, + ServerContext +} from '@modelcontextprotocol/server'; import { createLocalToolExecutionContext } from '../../utils/tracing.js'; import { BaseTool } from '../BaseTool.js'; import { mapAppCsp, type MapAppCsp } from '../../resources/ui-apps/mapAppCsp.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import { RenderMapInputSchema, type RenderMapInput @@ -114,11 +116,11 @@ export class RenderMapTool extends BaseTool< async run( rawInput: unknown, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - extra?: RequestHandlerExtra + + extra?: ServerContext ): Promise { const accessToken = - extra?.authInfo?.token || process.env.MAPBOX_ACCESS_TOKEN; + extra?.http?.authInfo?.token || process.env.MAPBOX_ACCESS_TOKEN; const owner = getOwnerKeyFromToken(accessToken); const toolContext = createLocalToolExecutionContext(this.name, 0); return await context.with( diff --git a/src/tools/resource-reader-tool/ResourceReaderTool.ts b/src/tools/resource-reader-tool/ResourceReaderTool.ts index dc71c560..113bbb05 100644 --- a/src/tools/resource-reader-tool/ResourceReaderTool.ts +++ b/src/tools/resource-reader-tool/ResourceReaderTool.ts @@ -2,8 +2,10 @@ // Licensed under the MIT License. import { BaseTool } from '../BaseTool.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; -import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; +import type { + CallToolResult, + ServerContext +} from '@modelcontextprotocol/server'; import type { ResourceReaderToolInput } from './ResourceReaderTool.input.schema.js'; import { ResourceReaderToolInputSchema } from './ResourceReaderTool.input.schema.js'; import { ResourceReaderToolOutputSchema } from './ResourceReaderTool.output.schema.js'; @@ -37,8 +39,8 @@ export class ResourceReaderTool extends BaseTool< async run( rawInput: unknown, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - extra?: RequestHandlerExtra + + extra?: ServerContext ): Promise { try { const input = this.inputSchema.parse(rawInput); @@ -72,8 +74,8 @@ export class ResourceReaderTool extends BaseTool< private async execute( input: ResourceReaderToolInput, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - extra?: RequestHandlerExtra + + extra?: ServerContext ): Promise { // Find the resource by URI const resource = getResourceByUri(input.uri); diff --git a/src/tools/reverse-geocode-tool/ReverseGeocodeTool.ts b/src/tools/reverse-geocode-tool/ReverseGeocodeTool.ts index 50634ecb..ef3d9aa6 100644 --- a/src/tools/reverse-geocode-tool/ReverseGeocodeTool.ts +++ b/src/tools/reverse-geocode-tool/ReverseGeocodeTool.ts @@ -3,7 +3,7 @@ import type { z } from 'zod'; import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import type { HttpRequest } from '../../utils/types.js'; import { ReverseGeocodeInputSchema } from './ReverseGeocodeTool.input.schema.js'; import { GeocodingResponseSchema } from './ReverseGeocodeTool.output.schema.js'; diff --git a/src/tools/search-and-geocode-tool/SearchAndGeocodeTool.ts b/src/tools/search-and-geocode-tool/SearchAndGeocodeTool.ts index 999342ba..277651c7 100644 --- a/src/tools/search-and-geocode-tool/SearchAndGeocodeTool.ts +++ b/src/tools/search-and-geocode-tool/SearchAndGeocodeTool.ts @@ -14,7 +14,7 @@ import type { MapboxFeatureCollection, MapboxFeature } from '../../schemas/geojson.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import { buildSearchAndGeocodeRequestUrl } from './buildSearchAndGeocodeRequestUrl.js'; import { renderHint } from '../../utils/storeMapPayload.js'; import { buildSelfFetchRef } from '../../utils/selfFetchRef.js'; diff --git a/src/tools/simplify-tool/SimplifyTool.ts b/src/tools/simplify-tool/SimplifyTool.ts index 24a4a490..55c52684 100644 --- a/src/tools/simplify-tool/SimplifyTool.ts +++ b/src/tools/simplify-tool/SimplifyTool.ts @@ -10,7 +10,7 @@ import { SimplifyOutputSchema, type SimplifyOutput } from './SimplifyTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; /** * SimplifyTool - Simplify geometries by reducing vertex count diff --git a/src/tools/static-map-image-tool/StaticMapImageTool.ts b/src/tools/static-map-image-tool/StaticMapImageTool.ts index e444e59d..37729d21 100644 --- a/src/tools/static-map-image-tool/StaticMapImageTool.ts +++ b/src/tools/static-map-image-tool/StaticMapImageTool.ts @@ -7,7 +7,7 @@ import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; import type { HttpRequest } from '../../utils/types.js'; import { StaticMapImageInputSchema } from './StaticMapImageTool.input.schema.js'; import type { OverlaySchema } from './StaticMapImageTool.input.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import { temporaryResourceManager } from '../../utils/temporaryResourceManager.js'; import { getOwnerKeyFromToken } from '../../utils/jwtUtils.js'; diff --git a/src/tools/union-tool/UnionTool.ts b/src/tools/union-tool/UnionTool.ts index 5985da68..bec01c1d 100644 --- a/src/tools/union-tool/UnionTool.ts +++ b/src/tools/union-tool/UnionTool.ts @@ -10,7 +10,7 @@ import { UnionOutputSchema, type UnionOutput } from './UnionTool.output.schema.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; import { renderHint } from '../../utils/storeMapPayload.js'; import { buildComputeRef } from '../../utils/computeRef.js'; diff --git a/src/tools/validateOutput.ts b/src/tools/validateOutput.ts index bf4f8bfd..3a8aaf51 100644 --- a/src/tools/validateOutput.ts +++ b/src/tools/validateOutput.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import type { ZodTypeAny } from 'zod'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; /** * Validates the structured content against the output schema. diff --git a/src/utils/index.ts b/src/utils/index.ts index e24863c3..3a28b6e8 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -58,8 +58,8 @@ export type { VersionInfo } from './versionUtils.js'; // Export server setup helpers for consumers building their own McpServer // (e.g. a hosted deployment) rather than running this package's entry point. -// Call publishJsonSchema2020(server) before installing any tool, and pass -// buildServerInstructions() as the server's instructions. +// Pass buildServerInstructions() as the server's +// instructions. publishJsonSchema2020 is a deprecated no-op on SDK v2. export { publishJsonSchema2020, toJsonSchema2020 } from './jsonSchema2020.js'; export { buildServerInstructions } from './serverInstructions.js'; diff --git a/src/utils/jsonSchema2020.ts b/src/utils/jsonSchema2020.ts index 1b9d93b4..b1ba04d8 100644 --- a/src/utils/jsonSchema2020.ts +++ b/src/utils/jsonSchema2020.ts @@ -1,8 +1,7 @@ // Copyright (c) Mapbox, Inc. // Licensed under the MIT License. -import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; +import type { McpServer } from '@modelcontextprotocol/server'; export const JSON_SCHEMA_2020_12 = 'https://json-schema.org/draft/2020-12/schema'; @@ -104,46 +103,14 @@ export function toJsonSchema2020(schema: unknown): unknown { } /** - * Make `tools/list` publish JSON Schema 2020-12 instead of draft-07. + * No-op, kept so callers written for SDK v1 keep working. * - * MCP SDK 1.x's McpServer generates every tool's inputSchema/outputSchema - * as draft-07 (with no option to change it), but MCP specifies 2020-12 as - * the default dialect, and newer clients enforce that — Claude Desktop - * refuses to call any tool whose outputSchema declares draft-07 ("JSON - * Schema declares an unsupported dialect ... The default validator - * supports JSON Schema 2020-12 only"). + * MCP SDK v1's McpServer published every tool schema as draft-07, which + * newer clients reject (Claude Desktop: "JSON Schema declares an + * unsupported dialect"), so this used to convert `tools/list` schemas to + * 2020-12. SDK v2 publishes 2020-12 itself. * - * McpServer installs its tools/list handler through the public - * setRequestHandler on first tool registration, so this wraps that method - * on this one server instance (not a global patch) and converts the - * schemas in the handler's result. Must be called before any tool is - * registered. + * @deprecated Not needed on SDK v2; remove the call. */ -export function publishJsonSchema2020(server: McpServer): void { - const inner = server.server; - const listToolsMethod = ListToolsRequestSchema.shape.method.value; - const original = inner.setRequestHandler.bind(inner); - - // setRequestHandler's generic signature can't be expressed for a wrapper - // without re-declaring the SDK's internal schema types. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (inner as any).setRequestHandler = (requestSchema: any, handler: any) => { - if (requestSchema?.shape?.method?.value !== listToolsMethod) { - return original(requestSchema, handler); - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return original(requestSchema, async (request: any, extra: any) => { - const result = await handler(request, extra); - return { - ...result, - tools: result.tools.map((tool: Schema) => ({ - ...tool, - inputSchema: toJsonSchema2020(tool.inputSchema), - ...(tool.outputSchema !== undefined && { - outputSchema: toJsonSchema2020(tool.outputSchema) - }) - })) - }; - }); - }; -} +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function publishJsonSchema2020(_server: McpServer): void {} diff --git a/src/utils/versionUtils-cjs.cjs b/src/utils/versionUtils-cjs.cjs index 7db3f24b..083500f2 100644 --- a/src/utils/versionUtils-cjs.cjs +++ b/src/utils/versionUtils-cjs.cjs @@ -1,48 +1,58 @@ -"use strict"; +'use strict'; // Copyright (c) Mapbox, Inc. // Licensed under the MIT License. -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, '__esModule', { value: true }); exports.getVersionInfo = getVersionInfo; -const node_fs_1 = require("node:fs"); -const node_path_1 = __importDefault(require("node:path")); +const node_fs_1 = require('node:fs'); +const node_path_1 = __importDefault(require('node:path')); function getVersionInfo() { - const name = 'Mapbox MCP server'; + const name = 'Mapbox MCP server'; + try { + const dirname = __dirname; + // Try to read from version.json first (for build artifacts) + const versionJsonPath = node_path_1.default.resolve( + dirname, + '..', + 'version.json' + ); try { - const dirname = __dirname; - // Try to read from version.json first (for build artifacts) - const versionJsonPath = node_path_1.default.resolve(dirname, '..', 'version.json'); - try { - const versionData = (0, node_fs_1.readFileSync)(versionJsonPath, 'utf-8'); - const info = JSON.parse(versionData); - info['name'] = name; - return info; - } - catch { - // Fall back to package.json - const packageJsonPath = node_path_1.default.resolve(dirname, '..', '..', '..', 'package.json'); - const packageData = (0, node_fs_1.readFileSync)(packageJsonPath, 'utf-8'); - const packageInfo = JSON.parse(packageData); - return { - name: name, - version: packageInfo.version || '0.0.0', - sha: 'unknown', - tag: 'unknown', - branch: 'unknown' - }; - } - } - catch (error) { - console.warn(`Failed to read version info: ${error}`); - return { - name: name, - version: '0.0.0', - sha: 'unknown', - tag: 'unknown', - branch: 'unknown' - }; + const versionData = (0, node_fs_1.readFileSync)(versionJsonPath, 'utf-8'); + const info = JSON.parse(versionData); + info['name'] = name; + return info; + } catch { + // Fall back to package.json + const packageJsonPath = node_path_1.default.resolve( + dirname, + '..', + '..', + '..', + 'package.json' + ); + const packageData = (0, node_fs_1.readFileSync)(packageJsonPath, 'utf-8'); + const packageInfo = JSON.parse(packageData); + return { + name: name, + version: packageInfo.version || '0.0.0', + sha: 'unknown', + tag: 'unknown', + branch: 'unknown' + }; } + } catch (error) { + console.warn(`Failed to read version info: ${error}`); + return { + name: name, + version: '0.0.0', + sha: 'unknown', + tag: 'unknown', + branch: 'unknown' + }; + } } -//# sourceMappingURL=versionUtils-cjs.cjs.map \ No newline at end of file +//# sourceMappingURL=versionUtils-cjs.cjs.map diff --git a/src/utils/versionUtils-cjs.d.cts b/src/utils/versionUtils-cjs.d.cts index 3f068751..a5985e15 100644 --- a/src/utils/versionUtils-cjs.d.cts +++ b/src/utils/versionUtils-cjs.d.cts @@ -1,9 +1,9 @@ export interface VersionInfo { - name: string; - version: string; - sha: string; - tag: string; - branch: string; + name: string; + version: string; + sha: string; + tag: string; + branch: string; } export declare function getVersionInfo(): VersionInfo; -//# sourceMappingURL=versionUtils-cjs.d.cts.map \ No newline at end of file +//# sourceMappingURL=versionUtils-cjs.d.cts.map diff --git a/test/exports.test.ts b/test/exports.test.ts index 6095c63f..c177c0ae 100644 --- a/test/exports.test.ts +++ b/test/exports.test.ts @@ -183,12 +183,10 @@ describe('Package exports', () => { // Mirrors how an external deployment (e.g. hosted-mcp-server) builds its // own McpServer from the subpath exports instead of running src/index.ts. it('should give an externally built server 2020-12 schemas and instructions', async () => { - const { McpServer } = - await import('@modelcontextprotocol/sdk/server/mcp.js'); - const { Client } = - await import('@modelcontextprotocol/sdk/client/index.js'); + const { McpServer } = await import('@modelcontextprotocol/server'); + const { Client } = await import('@modelcontextprotocol/client'); const { InMemoryTransport } = - await import('@modelcontextprotocol/sdk/inMemory.js'); + await import('@modelcontextprotocol/server'); const { publishJsonSchema2020, buildServerInstructions } = await import('../src/utils/index.js'); const { getAllTools } = await import('../src/tools/index.js'); diff --git a/test/integration/hostedMultiTaskFollowup.test.ts b/test/integration/hostedMultiTaskFollowup.test.ts index f7fdbfc9..271e48d0 100644 --- a/test/integration/hostedMultiTaskFollowup.test.ts +++ b/test/integration/hostedMultiTaskFollowup.test.ts @@ -6,8 +6,8 @@ import { existsSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { createServer, type Server } from 'node:http'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'; +import { Client } from '@modelcontextprotocol/client'; /** * Reproduces the hosted deployment's actual failure mode: a large diff --git a/test/integration/processRestart.test.ts b/test/integration/processRestart.test.ts index 774dd60a..208e50ad 100644 --- a/test/integration/processRestart.test.ts +++ b/test/integration/processRestart.test.ts @@ -5,8 +5,8 @@ import { describe, it, expect } from 'vitest'; import { existsSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'; +import { Client } from '@modelcontextprotocol/client'; /** * Spawns the *actual built server* (dist/esm/index.js — the same entry point diff --git a/test/patches/mcp-sdk-patch.test.ts b/test/patches/mcp-sdk-patch.test.ts deleted file mode 100644 index 6d9cd43f..00000000 --- a/test/patches/mcp-sdk-patch.test.ts +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) Mapbox, Inc. -// Licensed under the MIT License. - -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { z } from 'zod'; -import { BaseTool } from '../../src/tools/BaseTool.js'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; -import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js'; - -// Define test schemas -const TestInputSchema = z.object({ - input: z.string() -}); - -const TestOutputSchema = z.object({ - name: z.string(), - age: z.number(), - items: z.array(z.string()) // Expects array of strings -}); - -// Create a test tool that returns data violating its output schema -class TestToolWithMismatch extends BaseTool< - typeof TestInputSchema, - typeof TestOutputSchema -> { - readonly name = 'test-mismatch-tool'; - readonly description = 'Tool for testing schema mismatch handling'; - readonly annotations: ToolAnnotations = { - title: 'Test Mismatch Tool', - stabilityHint: 'experimental', - idempotentHint: true, - openWorldHint: false - }; - - constructor() { - super({ - inputSchema: TestInputSchema, - outputSchema: TestOutputSchema - }); - } - - async run( - rawInput: unknown, - extra?: RequestHandlerExtra - ): Promise { - // Parse input (would normally throw if invalid) - const input = this.inputSchema.parse(rawInput); - - // Return data that violates the output schema - return { - content: [{ type: 'text', text: `Processed: ${input.input}` }], - structuredContent: { - name: 'Test User', - age: 30, - items: 'single-string-not-array' // VIOLATION: Should be array! - }, - isError: false - }; - } -} - -// Tool that returns no structured content despite having output schema -class TestToolNoStructuredContent extends BaseTool< - typeof TestInputSchema, - typeof TestOutputSchema -> { - readonly name = 'test-no-structured'; - readonly description = 'Tool that omits structured content'; - readonly annotations: ToolAnnotations = { - title: 'Test No Structured Content', - stabilityHint: 'experimental', - idempotentHint: true, - openWorldHint: false - }; - - constructor() { - super({ - inputSchema: TestInputSchema, - outputSchema: TestOutputSchema - }); - } - - async run( - rawInput: unknown, - extra?: RequestHandlerExtra - ): Promise { - const input = this.inputSchema.parse(rawInput); - - // Return NO structured content despite having output schema - return { - content: [{ type: 'text', text: `Text only: ${input.input}` }], - // Note: no structuredContent field! - isError: false - }; - } -} - -describe('MCP SDK Output Validation Patch', () => { - let server: McpServer; - let consoleWarnSpy: ReturnType; - let consoleErrorSpy: ReturnType; - - beforeEach(() => { - server = new McpServer({ - name: 'test-server', - version: '1.0.0' - }); - - // Spy on console methods - consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - }); - - afterEach(() => { - consoleWarnSpy.mockRestore(); - consoleErrorSpy.mockRestore(); - }); - - it('should not throw when tool returns data that does not match output schema', async () => { - const tool = new TestToolWithMismatch(); - - // Register the tool - tool.installTo(server); - - // Execute the tool directly - let result: CallToolResult | undefined; - let errorThrown = false; - - try { - result = await tool.run({ input: 'test-input' }); - } catch (error) { - errorThrown = true; - console.log('Tool execution error:', error); - } - - // The tool itself should execute without error - expect(errorThrown).toBe(false); - expect(result).toBeDefined(); - expect(result!.isError).toBe(false); - expect(result!.structuredContent).toBeDefined(); - - // The mismatched data should still be in the result - expect(result!.structuredContent).toEqual({ - name: 'Test User', - age: 30, - items: 'single-string-not-array' // The invalid value is preserved - }); - }); - - it('should allow tools to return mismatched data without MCP errors', async () => { - // This test verifies that the patch prevents MCP-level validation errors - // We can't easily test the full MCP flow without a real transport, - // but we can verify that the tool executes successfully - const tool = new TestToolWithMismatch(); - tool.installTo(server); - - // The tool should execute without errors - const result = await tool.run({ input: 'test' }); - - // Result should contain the mismatched data - expect(result.isError).toBe(false); - expect(result.structuredContent).toEqual({ - name: 'Test User', - age: 30, - items: 'single-string-not-array' - }); - - // Note: The actual MCP validation happens when the server sends the response - // Our patch makes that validation non-fatal (logs warning instead of throwing) - }); - - it('should handle missing structured content gracefully', async () => { - const tool = new TestToolNoStructuredContent(); - tool.installTo(server); - - // Execute the tool - const result = await tool.run({ input: 'test' }); - - // Should succeed despite missing structured content - expect(result).toBeDefined(); - expect(result.isError).toBe(false); - expect(result.content[0].text).toBe('Text only: test'); - expect(result.structuredContent).toBeUndefined(); - }); - - describe('Patch Detection', () => { - it('should verify the MCP SDK patch is applied by checking the patched file', async () => { - // This test verifies the patch has been applied by checking for our patch marker - - // Read the patched SDK file - const fs = await import('node:fs'); - const path = await import('node:path'); - const { fileURLToPath } = await import('node:url'); - - // Get the SDK file path - const __dirname = path.dirname(fileURLToPath(import.meta.url)); - const sdkPath = path.join( - __dirname, - '..', - '..', - 'node_modules', - '@modelcontextprotocol', - 'sdk', - 'dist', - 'esm', - 'server', - 'mcp.js' - ); - - // Read the file content - const sdkContent = fs.readFileSync(sdkPath, 'utf-8'); - - // Check for our patch markers - const hasPatchMarker = sdkContent.includes('[MCP SDK Patch]'); - const hasWarningInsteadOfThrow = - sdkContent.includes('console.warn') && - sdkContent.includes('validation warning'); - - // Check that validation errors are NOT thrown - const stillThrowsValidationError = - sdkContent.includes('throw new McpError') && - sdkContent.includes('Output validation error'); - - if (!hasPatchMarker || !hasWarningInsteadOfThrow) { - throw new Error( - '🚨 MCP SDK PATCH IS NOT APPLIED! 🚨\n\n' + - 'The patch markers are missing from the SDK file.\n' + - 'This means the patch is not applied.\n\n' + - 'To fix:\n' + - '1. Run: npm run postinstall\n' + - "2. If that doesn't work, check patches/@modelcontextprotocol+sdk+*.patch exists\n" + - '3. If SDK was updated, recreate the patch:\n' + - ' - Modify node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js\n' + - ' - Run: npx patch-package @modelcontextprotocol/sdk' - ); - } - - if (stillThrowsValidationError) { - console.warn( - 'Warning: The SDK still contains throw statements for validation errors.\n' + - 'The patch may be partially applied or incomplete.' - ); - } - - // The patch is applied! - expect(hasPatchMarker).toBe(true); - expect(hasWarningInsteadOfThrow).toBe(true); - }); - - it('should verify patch works at runtime', async () => { - // Runtime verification - tool should work despite schema mismatch - const tool = new TestToolWithMismatch(); - const testServer = new McpServer({ - name: 'runtime-test', - version: '1.0.0' - }); - - // Install tool and verify it doesn't throw during registration - let registrationError: Error | undefined; - try { - tool.installTo(testServer); - } catch (error) { - registrationError = error as Error; - } - - expect(registrationError).toBeUndefined(); - - // Tool should execute successfully despite mismatched output - const result = await tool.run({ input: 'test' }); - expect(result.isError).toBe(false); - expect(result.structuredContent).toBeDefined(); - - // Check that we're getting the mismatched data (not sanitized) - expect(result.structuredContent).toHaveProperty( - 'items', - 'single-string-not-array' - ); - }); - }); -}); diff --git a/test/prompts/BasePrompt.test.ts b/test/prompts/BasePrompt.test.ts index 4d3416d8..67d3869c 100644 --- a/test/prompts/BasePrompt.test.ts +++ b/test/prompts/BasePrompt.test.ts @@ -6,7 +6,7 @@ import { BasePrompt } from '../../src/prompts/BasePrompt.js'; import type { PromptArgument, PromptMessage -} from '@modelcontextprotocol/sdk/types.js'; +} from '@modelcontextprotocol/server'; // Create a concrete test implementation of BasePrompt class TestPrompt extends BasePrompt { diff --git a/test/resources/BaseResource.test.ts b/test/resources/BaseResource.test.ts index 870ed4b7..fded8992 100644 --- a/test/resources/BaseResource.test.ts +++ b/test/resources/BaseResource.test.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { describe, it, expect } from 'vitest'; -import type { ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'; +import type { ReadResourceResult } from '@modelcontextprotocol/server'; import { BaseResource } from '../../src/resources/BaseResource.js'; describe('BaseResource', () => { diff --git a/test/resources/compute/ComputeResource.test.ts b/test/resources/compute/ComputeResource.test.ts index 03080671..25af05cf 100644 --- a/test/resources/compute/ComputeResource.test.ts +++ b/test/resources/compute/ComputeResource.test.ts @@ -88,7 +88,7 @@ describe('ComputeResource', () => { ]); // eslint-disable-next-line @typescript-eslint/no-explicit-any - const asAccountB = { authInfo: { token: 'irrelevant' } } as any; + const asAccountB = { http: { authInfo: { token: 'irrelevant' } } } as any; const result = await resource.read(ref, asAccountB); const payload = JSON.parse(result.contents[0].text as string); expect(payload.summary).toBe('Intersection of two polygons'); diff --git a/test/resources/temporary/TemporaryDataResource.test.ts b/test/resources/temporary/TemporaryDataResource.test.ts index 7ab1e5c1..97dfb4b3 100644 --- a/test/resources/temporary/TemporaryDataResource.test.ts +++ b/test/resources/temporary/TemporaryDataResource.test.ts @@ -41,7 +41,7 @@ function forgedTokenFor(username: string): string { // eslint-disable-next-line @typescript-eslint/no-explicit-any function extraFor(token?: string): any { - return token ? { authInfo: { token } } : {}; + return token ? { http: { authInfo: { token } } } : {}; } const NOT_FOUND = diff --git a/test/resources/ui-apps/MapAppUIResource.test.ts b/test/resources/ui-apps/MapAppUIResource.test.ts index 2b626fc0..01eedcdc 100644 --- a/test/resources/ui-apps/MapAppUIResource.test.ts +++ b/test/resources/ui-apps/MapAppUIResource.test.ts @@ -49,7 +49,7 @@ describe('MapAppUIResource', () => { const result = await resource.read('ui://mapbox/map-app/index.html', { // eslint-disable-next-line @typescript-eslint/no-explicit-any - authInfo: { token: SK_TOKEN } as any + http: { authInfo: { token: SK_TOKEN } as any } // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); @@ -82,7 +82,7 @@ describe('MapAppUIResource', () => { const result = await resource.read('ui://mapbox/map-app/index.html', { // eslint-disable-next-line @typescript-eslint/no-explicit-any - authInfo: { token: SK_TOKEN } as any + http: { authInfo: { token: SK_TOKEN } as any } // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); diff --git a/test/tools/BaseTool.test.ts b/test/tools/BaseTool.test.ts index dd886ca4..16695379 100644 --- a/test/tools/BaseTool.test.ts +++ b/test/tools/BaseTool.test.ts @@ -4,7 +4,7 @@ import { describe, it, expect, vi } from 'vitest'; import { BaseTool } from '../../src/tools/BaseTool.js'; import { z } from 'zod'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { CallToolResult } from '@modelcontextprotocol/server'; // Create a concrete test implementation of BaseTool class TestTool extends BaseTool< diff --git a/test/tools/annotations.test.ts b/test/tools/annotations.test.ts index 5c54c44c..68525d3a 100644 --- a/test/tools/annotations.test.ts +++ b/test/tools/annotations.test.ts @@ -3,7 +3,7 @@ import { describe, it, expect } from 'vitest'; import { getAllTools } from '../../src/tools/toolRegistry.js'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { McpServer } from '@modelcontextprotocol/server'; describe('Tool Annotations', () => { it('should have annotations for all tools', () => { diff --git a/test/tools/category-search-tool/CategorySearchTool.test.ts b/test/tools/category-search-tool/CategorySearchTool.test.ts index 43c9c16b..7063aefb 100644 --- a/test/tools/category-search-tool/CategorySearchTool.test.ts +++ b/test/tools/category-search-tool/CategorySearchTool.test.ts @@ -469,7 +469,7 @@ describe('CategorySearchTool', () => { proximity: { longitude: -122.42, latitude: 37.78 } }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); diff --git a/test/tools/difference-tool/DifferenceTool.test.ts b/test/tools/difference-tool/DifferenceTool.test.ts index a764d59b..26dc728a 100644 --- a/test/tools/difference-tool/DifferenceTool.test.ts +++ b/test/tools/difference-tool/DifferenceTool.test.ts @@ -203,7 +203,7 @@ describe('DifferenceTool', () => { ] }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); diff --git a/test/tools/directions-tool/DirectionsTool.test.ts b/test/tools/directions-tool/DirectionsTool.test.ts index 6fe88050..1bb1c479 100644 --- a/test/tools/directions-tool/DirectionsTool.test.ts +++ b/test/tools/directions-tool/DirectionsTool.test.ts @@ -1185,7 +1185,7 @@ describe('DirectionsTool', () => { geometries: 'geojson' }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); @@ -1237,7 +1237,7 @@ describe('DirectionsTool', () => { geometries: 'none' }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); @@ -1288,7 +1288,7 @@ describe('DirectionsTool', () => { geometries: 'geojson' }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); diff --git a/test/tools/ground-location-tool/GroundLocationTool.test.ts b/test/tools/ground-location-tool/GroundLocationTool.test.ts index bd076c36..08e2951d 100644 --- a/test/tools/ground-location-tool/GroundLocationTool.test.ts +++ b/test/tools/ground-location-tool/GroundLocationTool.test.ts @@ -331,7 +331,7 @@ describe('GroundLocationTool', () => { query: 'coffee' }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); diff --git a/test/tools/intersect-tool/IntersectTool.test.ts b/test/tools/intersect-tool/IntersectTool.test.ts index 04956072..9e74f4eb 100644 --- a/test/tools/intersect-tool/IntersectTool.test.ts +++ b/test/tools/intersect-tool/IntersectTool.test.ts @@ -203,7 +203,7 @@ describe('IntersectTool', () => { ] }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); diff --git a/test/tools/isochrone-tool/IsochroneTool.test.ts b/test/tools/isochrone-tool/IsochroneTool.test.ts index bfbae26e..d9cc5503 100644 --- a/test/tools/isochrone-tool/IsochroneTool.test.ts +++ b/test/tools/isochrone-tool/IsochroneTool.test.ts @@ -179,7 +179,7 @@ describe('IsochroneTool', () => { generalize: 1000 }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); diff --git a/test/tools/lenientOutputSchema.test.ts b/test/tools/lenientOutputSchema.test.ts new file mode 100644 index 00000000..dd676abc --- /dev/null +++ b/test/tools/lenientOutputSchema.test.ts @@ -0,0 +1,130 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { z } from 'zod'; +import { McpServer, InMemoryTransport } from '@modelcontextprotocol/server'; +import type { + CallToolResult, + ToolAnnotations +} from '@modelcontextprotocol/server'; +import { Client } from '@modelcontextprotocol/client'; +import { BaseTool } from '../../src/tools/BaseTool.js'; + +const InputSchema = z.object({ input: z.string() }); +const OutputSchema = z.object({ + name: z.string(), + items: z.array(z.string()) +}); + +/** A tool whose run() returns whatever result the test gives it. */ +class FixedResultTool extends BaseTool< + typeof InputSchema, + typeof OutputSchema +> { + readonly name = 'fixed_result_tool'; + readonly description = 'Returns a fixed result'; + readonly annotations: ToolAnnotations = { title: 'Fixed result' }; + + constructor(private readonly result: CallToolResult) { + super({ inputSchema: InputSchema, outputSchema: OutputSchema }); + } + + async run(): Promise { + return this.result; + } +} + +/** + * Calls the tool through a real McpServer. Uses a raw tools/call request, + * since the SDK client's callTool() runs its own output-schema check and + * this is about what the server sends. + */ +async function callThroughServer(result: CallToolResult) { + const server = new McpServer({ name: 'test', version: '0.0.0' }); + new FixedResultTool(result).installTo(server); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + const client = new Client({ name: 'test-client', version: '0.0.0' }); + await Promise.all([ + server.connect(serverTransport), + client.connect(clientTransport) + ]); + const { tools } = await client.listTools(); + const response = (await client.request({ + method: 'tools/call', + params: { name: 'fixed_result_tool', arguments: { input: 'x' } } + })) as CallToolResult; + await client.close(); + return { tools, response }; +} + +describe('lenient output schema', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('publishes the real output schema in tools/list', async () => { + const { tools } = await callThroughServer({ + content: [{ type: 'text', text: 'ok' }], + structuredContent: { name: 'a', items: [] } + }); + expect(tools[0].outputSchema).toMatchObject({ + type: 'object', + properties: { + name: { type: 'string' }, + items: { type: 'array', items: { type: 'string' } } + }, + required: ['name', 'items'] + }); + }); + + it('passes mismatched structured content through with a warning, not an error', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const { response } = await callThroughServer({ + content: [{ type: 'text', text: 'Processed' }], + structuredContent: { name: 'a', items: 'not-an-array' } + }); + + expect(response.isError).toBeFalsy(); + expect(response.structuredContent).toEqual({ + name: 'a', + items: 'not-an-array' + }); + expect(response.content).toEqual([{ type: 'text', text: 'Processed' }]); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining('fixed_result_tool') + ); + }); + + it('keeps a text-only answer instead of failing for missing structured content', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const { response } = await callThroughServer({ + content: [{ type: 'text', text: 'Text only' }] + }); + + expect(response.isError).toBeFalsy(); + expect(response.content).toEqual([{ type: 'text', text: 'Text only' }]); + expect(response.structuredContent).toEqual({}); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining('no structured content') + ); + }); + + it('leaves matching structured content and error results alone', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const ok = await callThroughServer({ + content: [{ type: 'text', text: 'ok' }], + structuredContent: { name: 'a', items: ['b'] } + }); + expect(ok.response.structuredContent).toEqual({ name: 'a', items: ['b'] }); + + const failed = await callThroughServer({ + content: [{ type: 'text', text: 'Upstream failed' }], + isError: true + }); + expect(failed.response.isError).toBe(true); + expect(failed.response.structuredContent).toBeUndefined(); + expect(warn).not.toHaveBeenCalled(); + }); +}); diff --git a/test/tools/map-matching-tool/MapMatchingTool.test.ts b/test/tools/map-matching-tool/MapMatchingTool.test.ts index c77e7203..35baff12 100644 --- a/test/tools/map-matching-tool/MapMatchingTool.test.ts +++ b/test/tools/map-matching-tool/MapMatchingTool.test.ts @@ -343,7 +343,7 @@ describe('MapMatchingTool', () => { profile: 'driving' }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); diff --git a/test/tools/optimization-tool/OptimizationTool.test.ts b/test/tools/optimization-tool/OptimizationTool.test.ts index 42530752..60d46823 100644 --- a/test/tools/optimization-tool/OptimizationTool.test.ts +++ b/test/tools/optimization-tool/OptimizationTool.test.ts @@ -291,7 +291,7 @@ describe('OptimizationTool V1 API', () => { ] }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); diff --git a/test/tools/render-map-tool/RenderMapTool.test.ts b/test/tools/render-map-tool/RenderMapTool.test.ts index db28643f..4715ee10 100644 --- a/test/tools/render-map-tool/RenderMapTool.test.ts +++ b/test/tools/render-map-tool/RenderMapTool.test.ts @@ -64,7 +64,7 @@ describe('RenderMapTool', () => { ] }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); @@ -116,7 +116,7 @@ describe('RenderMapTool', () => { markers: [{ coordinates: [-77, 38], style: 'start' }] }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); @@ -162,7 +162,7 @@ describe('RenderMapTool', () => { ] }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); @@ -230,7 +230,7 @@ describe('RenderMapTool', () => { const result = await tool.run( { payload_refs: [ref] }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); const sc = result.structuredContent as { @@ -308,7 +308,7 @@ describe('RenderMapTool', () => { const result = await tool.run( { payload_refs: [a, b] }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); const sc = result.structuredContent as { @@ -323,8 +323,10 @@ describe('RenderMapTool', () => { const tool = new RenderMapTool({ httpRequest: vi.fn() }); const result = await tool.run( { payload_refs: ['mapbox://temp/map-payload-does-not-exist'] }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token: tokenFor('account-test-render-map-stale') } } as any + + { + http: { authInfo: { token: tokenFor('account-test-render-map-stale') } } + } as any ); expect(result.isError).toBe(true); const text = ( @@ -368,7 +370,7 @@ describe('RenderMapTool', () => { const result = await tool.run( { payload_refs: [goodRef, 'mapbox://temp/map-payload-stale-one'] }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); const sc = result.structuredContent as { layer_count: number }; @@ -395,7 +397,7 @@ describe('RenderMapTool', () => { const result = await tool.run( { payload_refs: [badRef], summary: '15-min drive-time area' }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token: tokenFor('alice') } } as any + { http: { authInfo: { token: tokenFor('alice') } } } as any ); expect(result.isError).toBe(true); const text = (result.content[0] as { type: 'text'; text: string }).text; diff --git a/test/tools/search-and-geocode-tool/SearchAndGeocodeTool.test.ts b/test/tools/search-and-geocode-tool/SearchAndGeocodeTool.test.ts index 5386c6a1..8702b28b 100644 --- a/test/tools/search-and-geocode-tool/SearchAndGeocodeTool.test.ts +++ b/test/tools/search-and-geocode-tool/SearchAndGeocodeTool.test.ts @@ -699,7 +699,7 @@ describe('SearchAndGeocodeTool', () => { proximity: { longitude: -122.4194, latitude: 37.7749 } }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); diff --git a/test/tools/union-tool/UnionTool.test.ts b/test/tools/union-tool/UnionTool.test.ts index 076038a1..10339619 100644 --- a/test/tools/union-tool/UnionTool.test.ts +++ b/test/tools/union-tool/UnionTool.test.ts @@ -189,7 +189,7 @@ describe('UnionTool', () => { ] }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - { authInfo: { token } } as any + { http: { authInfo: { token } } } as any ); expect(result.isError).toBe(false); diff --git a/test/utils/jsonSchema2020.test.ts b/test/utils/jsonSchema2020.test.ts index d88e9d15..93ca8a84 100644 --- a/test/utils/jsonSchema2020.test.ts +++ b/test/utils/jsonSchema2020.test.ts @@ -2,12 +2,10 @@ // Licensed under the MIT License. import { describe, it, expect } from 'vitest'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'; +import { Client } from '@modelcontextprotocol/client'; +import { McpServer, InMemoryTransport } from '@modelcontextprotocol/server'; import { JSON_SCHEMA_2020_12, - publishJsonSchema2020, toJsonSchema2020 } from '../../src/utils/jsonSchema2020.js'; import { getAllTools } from '../../src/tools/toolRegistry.js'; @@ -138,10 +136,9 @@ describe('toJsonSchema2020', () => { }); }); -describe('publishJsonSchema2020', () => { - async function listTools(publish: boolean) { +describe('tools/list schemas', () => { + async function listTools() { const server = new McpServer({ name: 'test', version: '0.0.0' }); - if (publish) publishJsonSchema2020(server); for (const tool of getAllTools()) tool.installTo(server); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); @@ -157,9 +154,10 @@ describe('publishJsonSchema2020', () => { // Regression: Claude Desktop rejected every tool call with "Tool // 'search_and_geocode_tool' has an invalid outputSchema: JSON Schema - // declares an unsupported dialect" because the SDK emits draft-07. + // declares an unsupported dialect" because SDK v1 emitted draft-07. SDK v2 + // publishes 2020-12 itself; this guards against that regressing. it('publishes every tool schema as JSON Schema 2020-12', async () => { - const tools = await listTools(true); + const tools = await listTools(); expect(tools.length).toBe(getAllTools().length); for (const tool of tools) { expect(tool.inputSchema.$schema, tool.name).toBe(JSON_SCHEMA_2020_12); @@ -171,19 +169,8 @@ describe('publishJsonSchema2020', () => { } }); - it('is needed: the SDK alone publishes draft-07 with tuple items', async () => { - const tools = await listTools(false); - const leftovers = tools.flatMap((t) => [ - ...draft07Leftovers(t.inputSchema), - ...draft07Leftovers(t.outputSchema) - ]); - expect(leftovers.some((l) => l.endsWith('.$schema'))).toBe(true); - expect(leftovers.some((l) => l.endsWith('.items[]'))).toBe(true); - }); - it('keeps tool calls working with structured output validation', async () => { const server = new McpServer({ name: 'test', version: '0.0.0' }); - publishJsonSchema2020(server); const bbox = getAllTools().find((t) => t.name === 'bbox_tool')!; bbox.installTo(server); const [clientTransport, serverTransport] = From 7212f80cc25494d2fa59c9b4cd7dc96c26c41a4d Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Wed, 21 Oct 2026 15:19:32 -0400 Subject: [PATCH 2/2] docs: add PR number to CHANGELOG Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f28c00..b2968bb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### Breaking Changes -- **MCP TypeScript SDK v2 (#245).** The server and the `@mapbox/mcp-server/tools`, `/resources` and `/utils` exports now use the v2 SDK packages (`@modelcontextprotocol/server` and `@modelcontextprotocol/core`, 2.3.1) instead of `@modelcontextprotocol/sdk` 1.x, and `@modelcontextprotocol/ext-apps` moves from 1.x to 2.0.3. v2 is the line that gets new MCP features, such as the SDK's Skills extension support, and ext-apps 2.x requires it. +- **MCP TypeScript SDK v2 (#296, part of #245).** The server and the `@mapbox/mcp-server/tools`, `/resources` and `/utils` exports now use the v2 SDK packages (`@modelcontextprotocol/server` and `@modelcontextprotocol/core`, 2.3.1) instead of `@modelcontextprotocol/sdk` 1.x, and `@modelcontextprotocol/ext-apps` moves from 1.x to 2.0.3. v2 is the line that gets new MCP features, such as the SDK's Skills extension support, and ext-apps 2.x requires it. - **For code that imports our tools:** `installTo(server)` needs a v2 `McpServer`. A v1 server from `@modelcontextprotocol/sdk` won't work, so a deployment that builds its own server (the hosted server, for one) has to move to v2 in the same release. Tool and resource handlers now receive the v2 context: the access token is at `ctx.http.authInfo.token` instead of `extra.authInfo.token`. - **Clients:** nothing changes. The server still answers the `initialize` handshake and negotiates 2025-11-25 with current clients, and the MCP Apps wire protocol is the same in ext-apps 2.x, so `render_map_tool` renders as before. Adopting the 2026-07-28 protocol revision (MRTR in place of elicitation, the Sampling replacement) is separate work under #245. - **No more SDK patch:** the v1 SDK was patched so a tool's `structuredContent` that didn't match its output schema, or was missing, logged a warning instead of failing the call. v2 takes any Standard Schema as an output schema, so `lenientOutputSchema` wraps each tool's Zod schema: `tools/list` still publishes the real JSON Schema, and validation only warns. A tool with an output schema that returns only text gets `structuredContent: {}` instead of an error, since v2 checks for it before validating. `patches/` is now empty.