diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index baa1b6680..4fd4d35e9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,11 +58,47 @@ jobs: - run: pnpm test:all + test-runtimes: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - runtime: bun + version: "1.x" + - runtime: deno + version: v2.x + steps: + - uses: actions/checkout@v6 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + - name: Set up Bun + if: matrix.runtime == 'bun' + uses: oven-sh/setup-bun@v2 + with: + bun-version: ${{ matrix.version }} + - name: Set up Deno + if: matrix.runtime == 'deno' + uses: denoland/setup-deno@v2 + with: + deno-version: ${{ matrix.version }} + - run: pnpm install + - run: pnpm build:all + - name: Run ${{ matrix.runtime }} integration tests + run: pnpm --filter @modelcontextprotocol/test-integration test:integration:${{ matrix.runtime }} + publish: runs-on: ubuntu-latest if: github.event_name == 'release' environment: release - needs: [build, test] + needs: [build, test, test-runtimes] permissions: contents: read diff --git a/README.md b/README.md index 21dbec6a6..f068ec915 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The Model Context Protocol (MCP) allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. -This repository contains the TypeScript SDK implementation of the MCP specification and ships: +This repository contains the TypeScript SDK implementation of the MCP specification. It runs on **Node.js**, **Bun**, and **Deno**, and ships: - MCP **server** libraries (tools/resources/prompts, Streamable HTTP, stdio, auth helpers) - MCP **client** libraries (transports, high-level helpers, OAuth helpers) @@ -57,12 +57,20 @@ They are intentionally thin adapters: they should not introduce new MCP function ```bash npm install @modelcontextprotocol/server zod +# or +bun add @modelcontextprotocol/server zod +# or +deno add npm:@modelcontextprotocol/server npm:zod ``` ### Client ```bash npm install @modelcontextprotocol/client zod +# or +bun add @modelcontextprotocol/client zod +# or +deno add npm:@modelcontextprotocol/client npm:zod ``` ### Optional middleware packages diff --git a/docs/client-quickstart.md b/docs/client-quickstart.md index 452fa240b..140afbb5d 100644 --- a/docs/client-quickstart.md +++ b/docs/client-quickstart.md @@ -19,10 +19,13 @@ This quickstart assumes you have familiarity with: Before starting, ensure your system meets these requirements: -- Node.js 20 or higher installed +- Node.js 20 or higher installed (or **Bun** / **Deno** — the SDK supports all three runtimes) - Latest version of `npm` installed - An Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys) +> [!TIP] +> This tutorial uses Node.js and npm, but you can substitute `bun` or `deno` commands where appropriate. For example, use `bun add` instead of `npm install`, or run the client with `bun run` / `deno run`. + ## Set up your environment First, let's create and set up our project: diff --git a/docs/server-quickstart.md b/docs/server-quickstart.md index ffa49cec5..4ee04fd9c 100644 --- a/docs/server-quickstart.md +++ b/docs/server-quickstart.md @@ -36,6 +36,9 @@ node --version npm --version ``` +> [!TIP] +> The MCP SDK also works with **Bun** and **Deno**. This tutorial uses Node.js, but you can substitute `bun` or `deno` commands where appropriate. For HTTP-based servers on Bun or Deno, use `WebStandardStreamableHTTPServerTransport` instead of the Node.js-specific transport — see the [server guide](./server.md) for details. + ## Set up your environment First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/). diff --git a/test/integration/package.json b/test/integration/package.json index f4b97e4ca..da21238ed 100644 --- a/test/integration/package.json +++ b/test/integration/package.json @@ -29,7 +29,9 @@ "test:watch": "vitest", "start": "npm run server", "server": "tsx watch --clear-screen=false scripts/cli.ts server", - "client": "tsx scripts/cli.ts client" + "client": "tsx scripts/cli.ts client", + "test:integration:bun": "bun test test/server/bun.test.ts", + "test:integration:deno": "deno test --no-check --allow-net --allow-read --allow-env test/server/deno.test.ts" }, "devDependencies": { "@modelcontextprotocol/core": "workspace:^", diff --git a/test/integration/test/server/bun.test.ts b/test/integration/test/server/bun.test.ts new file mode 100644 index 000000000..229145aa8 --- /dev/null +++ b/test/integration/test/server/bun.test.ts @@ -0,0 +1,57 @@ +/** + * Bun integration test + * + * Verifies the MCP server and client packages work natively on Bun. + * Run with: bun test test/server/bun.test.ts + */ + +import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; +import { McpServer, WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server'; +// eslint-disable-next-line import/no-unresolved +import { afterAll, beforeAll, describe, expect, it } from 'bun:test'; +import * as z from 'zod/v4'; + +describe('MCP on Bun', () => { + let httpServer: ReturnType; + let transport: WebStandardStreamableHTTPServerTransport; + + beforeAll(async () => { + const mcpServer = new McpServer({ name: 'test-server', version: '1.0.0' }); + + mcpServer.registerTool( + 'greet', + { + description: 'Greet someone', + inputSchema: z.object({ name: z.string() }) + }, + async ({ name }) => ({ + content: [{ type: 'text' as const, text: `Hello, ${name}!` }] + }) + ); + + transport = new WebStandardStreamableHTTPServerTransport(); + await mcpServer.connect(transport); + + httpServer = Bun.serve({ + port: 0, + fetch: req => transport.handleRequest(req) + }); + }); + + afterAll(async () => { + await transport?.close(); + httpServer?.stop(); + }); + + it('should handle MCP tool calls', async () => { + const client = new Client({ name: 'test-client', version: '1.0.0' }); + const clientTransport = new StreamableHTTPClientTransport(new URL(`http://localhost:${httpServer.port}`)); + + await client.connect(clientTransport); + + const result = await client.callTool({ name: 'greet', arguments: { name: 'Bun' } }); + expect(result.content).toEqual([{ type: 'text', text: 'Hello, Bun!' }]); + + await client.close(); + }); +}); diff --git a/test/integration/test/server/deno.test.ts b/test/integration/test/server/deno.test.ts new file mode 100644 index 000000000..7d45f815c --- /dev/null +++ b/test/integration/test/server/deno.test.ts @@ -0,0 +1,53 @@ +/** + * Deno integration test + * + * Verifies the MCP server and client packages work natively on Deno. + * Run with: deno test --no-check --allow-net --allow-read --allow-env test/server/deno.test.ts + */ + +import assert from 'node:assert/strict'; + +import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; +import { McpServer, WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server'; +import * as z from 'zod/v4'; + +Deno.test({ + name: 'MCP tool calls work on Deno', + sanitizeOps: false, + sanitizeResources: false, + async fn() { + const mcpServer = new McpServer({ name: 'test-server', version: '1.0.0' }); + + mcpServer.registerTool( + 'greet', + { + description: 'Greet someone', + inputSchema: z.object({ name: z.string() }) + }, + async ({ name }) => ({ + content: [{ type: 'text' as const, text: `Hello, ${name}!` }] + }) + ); + + const transport = new WebStandardStreamableHTTPServerTransport(); + await mcpServer.connect(transport); + + const httpServer = Deno.serve({ port: 0 }, req => transport.handleRequest(req)); + const port = httpServer.addr.port; + + try { + const client = new Client({ name: 'test-client', version: '1.0.0' }); + const clientTransport = new StreamableHTTPClientTransport(new URL(`http://localhost:${port}`)); + + await client.connect(clientTransport); + + const result = await client.callTool({ name: 'greet', arguments: { name: 'Deno' } }); + assert.deepStrictEqual(result.content, [{ type: 'text', text: 'Hello, Deno!' }]); + + await client.close(); + } finally { + await transport.close(); + await httpServer.shutdown(); + } + } +}); diff --git a/test/integration/tsconfig.json b/test/integration/tsconfig.json index 353e6a708..5d68aa999 100644 --- a/test/integration/tsconfig.json +++ b/test/integration/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "@modelcontextprotocol/tsconfig", "include": ["./"], - "exclude": ["node_modules", "dist"], + "exclude": ["node_modules", "dist", "test/server/bun.test.ts", "test/server/deno.test.ts"], "compilerOptions": { "paths": { "*": ["./*"], diff --git a/test/integration/vitest.config.js b/test/integration/vitest.config.js index 38f030fdd..5aad0051c 100644 --- a/test/integration/vitest.config.js +++ b/test/integration/vitest.config.js @@ -1,3 +1,11 @@ +import { defineConfig, mergeConfig } from 'vitest/config'; import baseConfig from '../../common/vitest-config/vitest.config.js'; -export default baseConfig; +export default mergeConfig( + baseConfig, + defineConfig({ + test: { + exclude: ['**/dist/**', '**/bun.test.ts', '**/deno.test.ts'] + } + }) +);