KM

kriasoft/mcp-client-gen

开发工具
20 stars 0 forks 质量 55 趋势 55

Turn any MCP server into a type-safe TypeScript SDK in seconds - with OAuth 2.1 and multi-provider support

概览

Generate a typed TypeScript client for a remote MCP server (Streamable HTTP or SSE): point it at a URL, get a module with one method per tool. - — inputs and outputs come from the server's JSON Schemas - — each method calls the official MCP SDK and returns its result unchanged - — generated code imports only SDK types, never this package - — if the server asks, approve in the browser and generation continues the MCP SDK (and oauth-callback if the server uses OAuth): No OAuth? Connect with the SDK alone: await client.connect(new StreamableHTTPClientTransport(new URL(url))). The CLI prints the right snippet for your server after writing the file. Tools are methods. Results are the SDK's CallToolResult; when a tool declares an output schema, structuredContent is typed once you've checked isError: Prompts and resources, when the server has them, live in namespaces: In tests, pass a plain object instead of a real Client: the factory needs only the methods it calls.

README

MCP Client Generator

Generate a typed TypeScript client for a remote MCP server (Streamable HTTP or SSE): point it at a URL, get a module with one method per tool.

await notion.search({ query: "Meeting Notes" }); // instead of client.callTool({ name: "notion-search", arguments: … })
  • Typed — inputs and outputs come from the server’s JSON Schemas
  • Thin — each method calls the official MCP SDK and returns its result unchanged
  • No lock-in — generated code imports only SDK types, never this package
  • OAuth handled — if the server asks, approve in the browser and generation continues

Quick Start

1. Generate a module from the server’s URL:

npx mcp-client-gen https://mcp.notion.com/mcp -o src/notion.ts

2. Install the MCP SDK (and oauth-callback if the server uses OAuth):

npm install @modelcontextprotocol/client oauth-callback

3. Connect an SDK Client and wrap it:

import { Client } from "@modelcontextprotocol/client";
import { browserAuth } from "oauth-callback/mcp";
import { createNotionClient } from "./notion.js";

const client = new Client(
  { name: "my-app", version: "1.0.0" },
  { versionNegotiation: { mode: "auto" } }, // use the newest protocol the server speaks
);
const auth = browserAuth({
  serverUrl: "https://mcp.notion.com/mcp",
  redirectUri: "http://127.0.0.1:3000/callback",
  clientName: "my-app",
});
await auth.connect(client); // opens the browser when needed
await client.listTools(); // once: lets the SDK validate typed results

const notion = createNotionClient(client);
const result = await notion.search({ query: "Meeting Notes" });

No OAuth? Connect with the SDK alone: await client.connect(new StreamableHTTPClientTransport(new URL(url))). The CLI prints the right snippet for your server after writing the file.

Using the Client

Tools are methods. Results are the SDK’s CallToolResult; when a tool declares an output schema, structuredContent is typed once you’ve checked isError:

const result = await github.searchIssues({ query: "is:open" });
if (result.isError) throw new Error("search failed");
result.structuredContent.items; // typed

Prompts and resources, when the server has them, live in namespaces:

await github.prompts.summarizePr({ number: "42" });
await github.resources.read("repo://octo/app/README.md"); // any URI
await github.resources.issue({ owner: "octo", number: "42" }); // a URI template, filled in

In tests, pass a plain object instead of a real Client: the factory needs only the methods it calls.

const notion = createNotionClient({
  callTool: async () => ({ content: [{ type: "text", text: "stub" }] }),
  getPrompt: async () => ({ messages: [] }),
  readResource: async () => ({ contents: [] }),
});

Keeping It in Sync

A generated module is a snapshot of the server’s tools, prompts and resources. Regenerate when they change. Re-running on an unchanged server produces an identical file, so diffs show exactly what changed.

Connect your app the way generation did (versionNegotiation: { mode: "auto" } above). In the rare case where result types depend on the protocol version, the factory throws a clear error for a Client that negotiated a different one.

CLI

npx mcp-client-gen                      # print the module to stdout
npx mcp-client-gen  -o src/notion.ts    # write it to a file
npx mcp-client-gen  --name notion       # name it: createNotionClient (default: from the URL)

npx mcp-client-gen                           # pick servers from your MCP config files
npx mcp-client-gen -y                        # all of them → src/mcp/ (or mcp/)
npx mcp-client-gen -o                   # all of them → 
npx mcp-client-gen --config            # read this config file instead

npx mcp-client-gen ... --no-oauth            # never open a browser; fail instead (e.g. in CI)
npx mcp-client-gen ... --oauth-port 8080     # OAuth redirect port, if 3000 is taken

Without a URL, the CLI reads .mcp.json, .cursor/mcp.json and .vscode/mcp.json (a .local.json beside each takes precedence; details), skips local stdio servers, and writes one module per server. Files are written only if every server succeeds.

// .mcp.json
{
  "mcpServers": {
    "notion": { "url": "https://mcp.notion.com/mcp" },
    "github": {
      "url": "https://api.githubcopilot.com/mcp/",
      "headers": { "Authorization": "Bearer ${GITHUB_TOKEN}" },
    },
  },
}

${NAME}, ${NAME:-default} and ${env:NAME} expand from the environment. A server with a missing variable is skipped with a warning.

Authentication

While generating, an OAuth server opens your browser to approve access (redirect: http://127.0.0.1:3000/callback). Credentials are kept in memory for that run only.

In your app, auth belongs to the Client you connect. With browserAuth(), pass a store to remember credentials. If a server later asks for more permissions, the call throws UnauthorizedError; run auth.connect(client) again, then retry:

import { UnauthorizedError } from "@modelcontextprotocol/client";
import { browserAuth, fileStore } from "oauth-callback/mcp";

const auth = browserAuth({
  serverUrl: "https://mcp.notion.com/mcp",
  redirectUri: "http://127.0.0.1:3000/callback",
  clientName: "my-app",
  store: fileStore("/home/me/.config/my-app/notion.json"),
});
await auth.connect(client);

try {
  await notion.search({ query: "Meeting Notes" });
} catch (error) {
  if (!(error instanceof UnauthorizedError)) throw error;
  await auth.connect(client);
  await notion.search({ query: "Meeting Notes" });
}

Programmatic API

import { generateClientModule } from "mcp-client-gen";

const source = await generateClientModule("https://mcp.notion.com/mcp", {
  name: "notion",
});

It returns the module’s source and writes nothing. Pass { url, transport, headers } instead of a URL for headers or legacy SSE. Options: name, oauth (false to never open a browser), fetch, timeout, signal. Details: docs/specs/api.md.

Requirements

Node.js 22+ (or Bun) to generate. Generated modules need @modelcontextprotocol/client ^2.2; with TypeScript 6+, add "types": ["node"] to your tsconfig.json (the SDK’s types use Node’s Buffer).

License

MIT — Konstantin Tarkus

View this README on GitHub

安装

npx mcp-client-gen https://mcp.notion.com/mcp -o src/notion.ts

配置

// .mcp.json { "mcpServers": { "notion": { "url": "https://mcp.notion.com/mcp" }, "github": { "url": "https://api.githubcopilot.com/mcp/", "headers": { "Authorization": "Bearer ${GITHUB_TOKEN}" }, }, }, }