Skip to content

Type MCP registerNeonTools against the matching McpServer - #567

Open
andrelandgraf wants to merge 3 commits into
mainfrom
tools-t7-mcp-server
Open

andrelandgraf wants to merge 3 commits into
mainfrom
tools-t7-mcp-server

Conversation

@andrelandgraf

@andrelandgraf andrelandgraf commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Problem

@neon/tools ships two MCP entry points that publish tool schemas in different formats: @neon/tools/mcp converts each tool's Zod input schema to JSON Schema for MCP 2.x and @neon/tools/mcp-v1 passes the Zod schema through for MCP 1.x. Both typed server as { registerTool: object }, so any object with a registerTool property satisfied either entry. Registering an MCP 1 McpServer through @neon/tools/mcp compiled clean and the mistake only surfaced at runtime, when the server received schemas in the other version's format.

What changed

Each entry now types server against the McpServer of its own SDK:

// @neon/tools/mcp
import type { McpServer } from "@modelcontextprotocol/server";
export type McpToolServer = Pick<McpServer, "registerTool">;

// @neon/tools/mcp-v1
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
export type McpToolServer = Pick<McpServer, "registerTool">;

@modelcontextprotocol/server (^2.0.0) and @modelcontextprotocol/sdk (^1.0.0) are optional peer dependencies. A consumer installs the one their server uses.

The correct pairing is unchanged:

import { McpServer } from "@modelcontextprotocol/server";
import { createNeonTools } from "@neon/tools";
import { registerNeonTools } from "@neon/tools/mcp";

const server = new McpServer({ name: "neon", version: "1.0.0" });
const tools = createNeonTools({ apiKey, tools: ["projects.list"] as const });
registerNeonTools(server, tools);

The wrong pairing now fails to compile:

error TS2345: Argument of type 'McpServer' is not assignable to parameter of type 'McpToolServer'.

This is a breaking type change. McpToolServer was one shared export with shape { registerTool: object }; each entry now exports its own Pick over the matching SDK, and hand-rolled stubs that satisfied the old shape need a cast. The changeset marks @neon/tools major. Runtime behavior is unchanged: registerNeonToolsWithSchema still treats registerTool as unknown internally, so JavaScript consumers see no difference.

Residual gap

The guard needs the matching peer installed. When it is missing and skipLibCheck is on, the McpServer import fails to resolve, the type degrades and an MCP 1 server type-checks against @neon/tools/mcp again. Both peers are optional, so package managers do not warn about the missing one. The README documents this next to the MCP example.

Also in here

  • The internal type in lib/mcp-register.ts is renamed to McpToolRegistrar (registerTool: unknown) since the public McpToolServer exports moved into the entry points.
  • @modelcontextprotocol/sdk 1.30.0 is added as a devDependency so the repo type-checks the v1 entry against the real SDK; pnpm-lock.yaml updated.
  • src/mcp.test.ts stubs are consolidated into captureHandler(), which returns the stub cast to each entry's McpToolServer.

Verification

  • pnpm --filter @neon/tools exec tsc --noEmit passes. It includes the new src/mcp.test-d.ts, which asserts both real servers type-check against their entry and a bare { registerTool: {} } object fails both.
  • A scratch type test passing the MCP 1 McpServer to the MCP 2 entry and the MCP 2 McpServer to the MCP 1 entry fails both directions with TS2345.
  • pnpm --filter @neon/tools exec vitest run src/mcp.test.ts: 13 tests pass.
  • Not verified: the missing-peer degrade in a consumer project with skipLibCheck on. The README note is the only mitigation in this PR.

Without the peer and with skipLibCheck, an MCP 1 server still type-checks against the MCP 2 adapter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant