From 049f26ef533e58444789b0f12ae5eb536bd40873 Mon Sep 17 00:00:00 2001 From: Konstantin Konstantinov Date: Thu, 5 Mar 2026 21:38:17 +0200 Subject: [PATCH] `v2` ci: add Bun and Deno integration tests (#1607) Co-authored-by: Claude Opus 4.6 --- .github/workflows/main.yml | 38 ++++++++++++++- README.md | 10 +++- docs/client-quickstart.md | 5 +- docs/server-quickstart.md | 3 ++ test/integration/package.json | 4 +- test/integration/test/server/bun.test.ts | 57 +++++++++++++++++++++++ test/integration/test/server/deno.test.ts | 53 +++++++++++++++++++++ test/integration/tsconfig.json | 2 +- test/integration/vitest.config.js | 10 +++- 9 files changed, 176 insertions(+), 6 deletions(-) create mode 100644 test/integration/test/server/bun.test.ts create mode 100644 test/integration/test/server/deno.test.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index baa1b668..4fd4d35e 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 21dbec6a..f068ec91 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 452fa240..140afbb5 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 ffa49cec..4ee04fd9 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 f4b97e4c..da21238e 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 00000000..229145aa --- /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 00000000..7d45f815 --- /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 353e6a70..5d68aa99 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 38f030fd..5aad0051 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'] + } + }) +);