diff --git a/.changeset/ai-mcp-carve-out.md b/.changeset/ai-mcp-carve-out.md new file mode 100644 index 0000000..5ca8e06 --- /dev/null +++ b/.changeset/ai-mcp-carve-out.md @@ -0,0 +1,7 @@ +--- +"@gemstack/ai-sdk": minor +--- + +Remove the `./mcp` subpath. The agent<->MCP bridge (`mcpClientTools` / `mcpServerFromAgent`) has moved to its own package, `@gemstack/ai-mcp`, so the optional `@modelcontextprotocol/sdk` peer dependency is now declared only by the package that uses it (and no longer surfaces to every `@gemstack/ai-sdk` consumer). + +Migration: replace `@gemstack/ai-sdk/mcp` imports with `@gemstack/ai-mcp`, and move the `@modelcontextprotocol/sdk` peer to that package. The bridge API is unchanged. diff --git a/.changeset/ai-mcp-initial.md b/.changeset/ai-mcp-initial.md new file mode 100644 index 0000000..14f2d0a --- /dev/null +++ b/.changeset/ai-mcp-initial.md @@ -0,0 +1,10 @@ +--- +"@gemstack/ai-mcp": minor +--- + +Initial release. The agent<->MCP bridge, carved out of `@gemstack/ai-sdk`'s former `./mcp` subpath: + +- `mcpClientTools(transport, opts?)` — consume a remote MCP server's tools as `@gemstack/ai-sdk` Agent tools (HTTP URL / stdio spawn / connected SDK client). +- `mcpServerFromAgent(AgentClass, opts?)` — expose an Agent as an MCP server, with `'tools'` / `'agent'` / `'both'` exposure modes. + +Depends on `@gemstack/ai-sdk`; `@modelcontextprotocol/sdk` is an optional peer. diff --git a/packages/ai-mcp/README.md b/packages/ai-mcp/README.md new file mode 100644 index 0000000..62c0527 --- /dev/null +++ b/packages/ai-mcp/README.md @@ -0,0 +1,58 @@ +# @gemstack/ai-mcp + +The bridge between [`@gemstack/ai-sdk`](https://github.com/gemstack-land/gemstack/tree/main/packages/ai-sdk) Agents and [Model Context Protocol](https://modelcontextprotocol.io) servers. Two connectors: + +- **`mcpClientTools(transport, opts?)`** — consume a remote MCP server's tools as Agent tools. +- **`mcpServerFromAgent(AgentClass, opts?)`** — expose an Agent as an MCP server external clients (Claude Desktop, Cursor, etc.) can call. + +This is the **agent bridge** axis of MCP. It depends on `@gemstack/ai-sdk` and is useless without an Agent. It was carved out of `@gemstack/ai-sdk`'s `/mcp` subpath so the optional MCP SDK dependency is declared only by the package that actually needs it. + +## Which MCP package do I use? + +> **Exposing an existing Agent, or feeding remote MCP tools into one?** Use `@gemstack/ai-mcp` (this package). +> **Authoring an MCP server from scratch** (tools / resources / prompts / auth)? Use a standalone MCP server framework — that is a separate, agent-agnostic concern, not this bridge. + +Both can "produce an MCP server", but from different inputs: `mcpServerFromAgent(anAgent)` versus a hand-authored server. That overlap is expected, not duplication. + +## Installation + +```bash +pnpm add @gemstack/ai-mcp @modelcontextprotocol/sdk +``` + +`@modelcontextprotocol/sdk` is an **optional peer dependency** — install it when you use this bridge. `@gemstack/ai-sdk` comes in as a regular dependency. + +## Usage + +### Consume a remote MCP server's tools + +```ts +import { mcpClientTools } from '@gemstack/ai-mcp' + +// (a) HTTP — string URL or URL instance +const tools = await mcpClientTools('https://api.example.com/mcp') + +// (b) Local stdio subprocess +const tools = await mcpClientTools({ command: 'npx', args: ['some-mcp-server'] }) + +// (c) Already-connected SDK Client (caller owns lifecycle) +const tools = await mcpClientTools(myClient) + +// Spread into your Agent's tools(). Call tools.close() when done (cases a + b). +``` + +### Expose an Agent as an MCP server + +```ts +import { mcpServerFromAgent } from '@gemstack/ai-mcp' +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' + +const server = await mcpServerFromAgent(MyAgent) +await server.connect(new StdioServerTransport()) +``` + +Three exposure modes via `opts.expose`: `'tools'` (default, one MCP tool per `agent.tools()` entry), `'agent'` (one tool that runs the whole agent: `prompt(text) → text`), or `'both'`. + +## License + +MIT diff --git a/packages/ai-mcp/package.json b/packages/ai-mcp/package.json new file mode 100644 index 0000000..3942634 --- /dev/null +++ b/packages/ai-mcp/package.json @@ -0,0 +1,66 @@ +{ + "name": "@gemstack/ai-mcp", + "version": "0.0.0", + "description": "Bridge between @gemstack/ai-sdk Agents and Model Context Protocol servers: consume remote MCP tools as Agent tools, and expose an Agent as an MCP server.", + "keywords": [ + "ai", + "agent", + "agents", + "mcp", + "model-context-protocol", + "tools", + "tool-calling", + "bridge", + "gemstack" + ], + "license": "MIT", + "homepage": "https://github.com/gemstack-land/gemstack/tree/main/packages/ai-mcp#readme", + "bugs": { + "url": "https://github.com/gemstack-land/gemstack/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/gemstack-land/gemstack", + "directory": "packages/ai-mcp" + }, + "type": "module", + "engines": { + "node": ">=22.12.0" + }, + "files": [ + "dist" + ], + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "build": "tsc -p tsconfig.build.json", + "dev": "tsc -p tsconfig.build.json --watch", + "typecheck": "tsc --noEmit", + "test": "tsc -p tsconfig.test.json && cd dist-test && node --test", + "clean": "rm -rf dist" + }, + "dependencies": { + "@gemstack/ai-sdk": "workspace:^", + "zod": "^4.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.29.0" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + }, + "devDependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "@types/node": "^20.0.0", + "typescript": "^5.4.0" + }, + "author": "Suleiman Shahbari" +} diff --git a/packages/ai-sdk/src/mcp-client-tools.test.ts b/packages/ai-mcp/src/client-tools.test.ts similarity index 98% rename from packages/ai-sdk/src/mcp-client-tools.test.ts rename to packages/ai-mcp/src/client-tools.test.ts index d5b9209..e5636ee 100644 --- a/packages/ai-sdk/src/mcp-client-tools.test.ts +++ b/packages/ai-mcp/src/client-tools.test.ts @@ -4,8 +4,8 @@ import { z } from 'zod' import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js' -import { mcpClientTools } from './mcp/client-tools.js' -import { toolToSchema } from './tool.js' +import { mcpClientTools } from './client-tools.js' +import { toolToSchema } from '@gemstack/ai-sdk' // ─── Helpers ───────────────────────────────────────────── diff --git a/packages/ai-sdk/src/mcp/client-tools.ts b/packages/ai-mcp/src/client-tools.ts similarity index 97% rename from packages/ai-sdk/src/mcp/client-tools.ts rename to packages/ai-mcp/src/client-tools.ts index fbe48a1..e0f8186 100644 --- a/packages/ai-sdk/src/mcp/client-tools.ts +++ b/packages/ai-mcp/src/client-tools.ts @@ -1,11 +1,11 @@ import { z } from 'zod' -import { dynamicTool } from '../tool.js' -import type { Tool, ToolCallContext } from '../types.js' +import { dynamicTool } from '@gemstack/ai-sdk' +import type { Tool, ToolCallContext } from '@gemstack/ai-sdk' import type { McpClientTransport, McpClientToolsOptions, StdioServerSpawn, } from './types.js' -const CLIENT_INFO = { name: 'rudderjs-ai-mcp-bridge', version: '1.0.0' } as const +const CLIENT_INFO = { name: 'gemstack-ai-mcp-bridge', version: '1.0.0' } as const /** * The result of `mcpClientTools()` — an array of `Tool`s that also carries a @@ -21,7 +21,7 @@ export interface McpClientToolsHandle extends ReadonlyArray { } /** - * Connect to a remote MCP server and surface its tools as Rudder `Tool`s. + * Connect to a remote MCP server and surface its tools as `@gemstack/ai-sdk` `Tool`s. * * Three transport shapes are accepted: * diff --git a/packages/ai-sdk/src/mcp/index.ts b/packages/ai-mcp/src/index.ts similarity index 88% rename from packages/ai-sdk/src/mcp/index.ts rename to packages/ai-mcp/src/index.ts index 4bb583d..0c26fb2 100644 --- a/packages/ai-sdk/src/mcp/index.ts +++ b/packages/ai-mcp/src/index.ts @@ -1,5 +1,5 @@ /** - * `@gemstack/ai-sdk/mcp` — bridge between `@gemstack/ai-sdk` Agents and Model Context + * `@gemstack/ai-mcp` — bridge between `@gemstack/ai-sdk` Agents and Model Context * Protocol servers. Two connectors: * * - {@link mcpClientTools} — consume a remote MCP server's tools as Agent tools diff --git a/packages/ai-sdk/src/mcp-server-from-agent-modes.test.ts b/packages/ai-mcp/src/server-from-agent-modes.test.ts similarity index 97% rename from packages/ai-sdk/src/mcp-server-from-agent-modes.test.ts rename to packages/ai-mcp/src/server-from-agent-modes.test.ts index 9450c6e..5f9f473 100644 --- a/packages/ai-sdk/src/mcp-server-from-agent-modes.test.ts +++ b/packages/ai-mcp/src/server-from-agent-modes.test.ts @@ -3,13 +3,11 @@ import assert from 'node:assert/strict' import { z } from 'zod' import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js' -import { Agent } from './agent.js' -import { AiRegistry } from './registry.js' -import { toolDefinition } from './tool.js' -import { mcpServerFromAgent } from './mcp/server-from-agent.js' +import { Agent, AiRegistry, toolDefinition } from '@gemstack/ai-sdk' +import { mcpServerFromAgent } from './server-from-agent.js' import type { AiMessage, ProviderAdapter, ProviderRequestOptions, ProviderResponse, StreamChunk, -} from './types.js' +} from '@gemstack/ai-sdk' // ─── Scripted adapter (copy of handoff.test.ts pattern) ─── diff --git a/packages/ai-sdk/src/mcp-server-from-agent.test.ts b/packages/ai-mcp/src/server-from-agent.test.ts similarity index 95% rename from packages/ai-sdk/src/mcp-server-from-agent.test.ts rename to packages/ai-mcp/src/server-from-agent.test.ts index d5be3fa..787e9ae 100644 --- a/packages/ai-sdk/src/mcp-server-from-agent.test.ts +++ b/packages/ai-mcp/src/server-from-agent.test.ts @@ -3,11 +3,8 @@ import assert from 'node:assert/strict' import { z } from 'zod' import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js' -import { Agent } from './agent.js' -import { AiFake } from './fake.js' -import { AiRegistry } from './registry.js' -import { toolDefinition } from './tool.js' -import { mcpServerFromAgent } from './mcp/server-from-agent.js' +import { Agent, AiFake, AiRegistry, toolDefinition } from '@gemstack/ai-sdk' +import { mcpServerFromAgent } from './server-from-agent.js' // ─── Fixture agent ──────────────────────────────────────── diff --git a/packages/ai-sdk/src/mcp/server-from-agent.ts b/packages/ai-mcp/src/server-from-agent.ts similarity index 97% rename from packages/ai-sdk/src/mcp/server-from-agent.ts rename to packages/ai-mcp/src/server-from-agent.ts index dc50e43..851112c 100644 --- a/packages/ai-sdk/src/mcp/server-from-agent.ts +++ b/packages/ai-mcp/src/server-from-agent.ts @@ -1,6 +1,6 @@ import { z } from 'zod' -import type { Agent } from '../agent.js' -import type { HasTools, Tool, ToolCallContext } from '../types.js' +import type { Agent } from '@gemstack/ai-sdk' +import type { HasTools, Tool, ToolCallContext } from '@gemstack/ai-sdk' import type { McpServerFromAgentOptions } from './types.js' /** @@ -11,7 +11,7 @@ import type { McpServerFromAgentOptions } from './types.js' * SDK's stdio / HTTP transports: * * ```ts - * import { mcpServerFromAgent } from '@gemstack/ai-sdk/mcp' + * import { mcpServerFromAgent } from '@gemstack/ai-mcp' * import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' * * const server = await mcpServerFromAgent(MyAgent) diff --git a/packages/ai-sdk/src/mcp/types.ts b/packages/ai-mcp/src/types.ts similarity index 97% rename from packages/ai-sdk/src/mcp/types.ts rename to packages/ai-mcp/src/types.ts index 14790df..38cf167 100644 --- a/packages/ai-sdk/src/mcp/types.ts +++ b/packages/ai-mcp/src/types.ts @@ -1,5 +1,5 @@ /** - * Public types for `@gemstack/ai-sdk/mcp`. Kept in a separate module so the + * Public types for `@gemstack/ai-mcp`. Kept in a separate module so the * client + server connectors can share them without circular imports. */ diff --git a/packages/ai-mcp/tsconfig.build.json b/packages/ai-mcp/tsconfig.build.json new file mode 100644 index 0000000..e578064 --- /dev/null +++ b/packages/ai-mcp/tsconfig.build.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "dist", "rootDir": "src" }, + "include": ["src"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/ai-mcp/tsconfig.json b/packages/ai-mcp/tsconfig.json new file mode 100644 index 0000000..404aab4 --- /dev/null +++ b/packages/ai-mcp/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "noEmit": true, "rootDir": "src" }, + "include": ["src"] +} diff --git a/packages/ai-mcp/tsconfig.test.json b/packages/ai-mcp/tsconfig.test.json new file mode 100644 index 0000000..eebda2f --- /dev/null +++ b/packages/ai-mcp/tsconfig.test.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "dist-test", "rootDir": "src" }, + "include": ["src"] +} diff --git a/packages/ai-sdk/README.md b/packages/ai-sdk/README.md index 19fef91..babe207 100644 --- a/packages/ai-sdk/README.md +++ b/packages/ai-sdk/README.md @@ -1,6 +1,6 @@ # @gemstack/ai-sdk -AI engine: providers, agents, tools, streaming, middleware, structured output, conversation memory, evals, MCP, computer-use, and testing fakes. +AI engine: providers, agents, tools, streaming, middleware, structured output, conversation memory, evals, computer-use, and testing fakes. The first [GemStack](https://github.com/gemstack-land/gemstack) package. Spun out of Rudder's `@rudderjs/ai` (carried forward from the 1.17.x line, renamed and re-versioned under the GemStack umbrella). The Rudder package now ships as a thin deprecated re-export of this one. @@ -40,7 +40,6 @@ The neutral storage contracts (`UserMemory`, `ConversationStore`, `BudgetStorage | `.` | Core: `Agent`, `tool`, streaming, middleware, facade | | `./server` | The server provider entry | | `./node` | Node-only entry | -| `./mcp` | Model Context Protocol server/client helpers | | `./computer-use` | Computer-use tool + executor | | `./eval` | Eval framework (`evalSuite`, metrics, reporters) | | `./gateway` | Gateway helpers | @@ -48,6 +47,8 @@ The neutral storage contracts (`UserMemory`, `ConversationStore`, `BudgetStorage | `./memory-embedding` | Embedding-backed user memory | | `./react` | React bindings | +> **Moved in `0.3.0`:** the MCP bridge (`mcpClientTools` / `mcpServerFromAgent`), previously the `./mcp` subpath, is now its own package, [`@gemstack/ai-mcp`](https://github.com/gemstack-land/gemstack/tree/main/packages/ai-mcp). Update `@gemstack/ai-sdk/mcp` imports to `@gemstack/ai-mcp` and move the `@modelcontextprotocol/sdk` peer there. + ## License MIT diff --git a/packages/ai-sdk/package.json b/packages/ai-sdk/package.json index 3f8d040..a86cf59 100644 --- a/packages/ai-sdk/package.json +++ b/packages/ai-sdk/package.json @@ -11,8 +11,6 @@ "tools", "tool-calling", "streaming", - "mcp", - "model-context-protocol", "computer-use", "eval", "structured-output", @@ -73,10 +71,6 @@ "import": "./dist/observers.js", "types": "./dist/observers.d.ts" }, - "./mcp": { - "import": "./dist/mcp/index.js", - "types": "./dist/mcp/index.d.ts" - }, "./chat-mentions": { "import": "./dist/chat-mentions.js", "types": "./dist/chat-mentions.d.ts" @@ -125,7 +119,6 @@ "zod": "^4.0.0" }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", "@rudderjs/console": "^1.4.3", "@rudderjs/core": "^1.13.3", "@rudderjs/orm": "^1.22.0", @@ -141,9 +134,6 @@ "@rudderjs/orm": { "optional": true }, - "@modelcontextprotocol/sdk": { - "optional": true - }, "react": { "optional": true } @@ -156,7 +146,6 @@ "openai": ">=4.70.0" }, "devDependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", "@rudderjs/console": "^1.4.3", "@rudderjs/core": "^1.13.3", "@rudderjs/orm": "^1.22.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d566e19..940de42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,11 @@ importers: specifier: ^5.4.0 version: 5.9.3 - packages/ai-sdk: + packages/ai-mcp: dependencies: + '@gemstack/ai-sdk': + specifier: workspace:^ + version: link:../ai-sdk zod: specifier: ^4.0.0 version: 4.4.3 @@ -27,6 +30,19 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.29.0 version: 1.29.0(zod@4.4.3) + '@types/node': + specifier: ^20.0.0 + version: 20.19.43 + typescript: + specifier: ^5.4.0 + version: 5.9.3 + + packages/ai-sdk: + dependencies: + zod: + specifier: ^4.0.0 + version: 4.4.3 + devDependencies: '@rudderjs/console': specifier: ^1.4.3 version: 1.4.3