@nestjs-adk/mcp 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # @nestjs-adk/mcp
2
+
3
+ Consume external MCP servers as agent tools for [`@nestjs-adk/core`](https://www.npmjs.com/package/@nestjs-adk/core), via the official `@modelcontextprotocol/sdk` (stdio, streamable HTTP, SSE).
4
+
5
+ ```ts
6
+ McpModule.forRoot({ servers: [{ name: "github", transport: { type: "stdio", command: "npx", args: [...] } }] })
7
+
8
+ @Agent({ ..., tools: [toolset("github", ["create_issue"])] }) // the server's catalog becomes tools (JSON Schema → Zod)
9
+ ```
10
+
11
+ The catalog is cached at boot (fail-fast; `optional: true` for non-critical servers). Runtime tool failures return as `{ error }` to the LLM; connection errors become typed `McpConnectionError`s.
12
+
13
+ Full documentation: [github.com/gabrieljsilva/nestjs-adk](https://github.com/gabrieljsilva/nestjs-adk)
package/dist/index.cjs ADDED
@@ -0,0 +1,215 @@
1
+ 'use strict';
2
+
3
+ var core = require('@nestjs-adk/core');
4
+ var index_js = require('@modelcontextprotocol/sdk/client/index.js');
5
+ var sse_js = require('@modelcontextprotocol/sdk/client/sse.js');
6
+ var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
7
+ var streamableHttp_js = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
8
+ var common = require('@nestjs/common');
9
+ var zod = require('zod');
10
+
11
+ /******************************************************************************
12
+ Copyright (c) Microsoft Corporation.
13
+
14
+ Permission to use, copy, modify, and/or distribute this software for any
15
+ purpose with or without fee is hereby granted.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
18
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
19
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
20
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
21
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
22
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
23
+ PERFORMANCE OF THIS SOFTWARE.
24
+ ***************************************************************************** */
25
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
26
+
27
+
28
+ function __decorate(decorators, target, key, desc) {
29
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
30
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
31
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
32
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
33
+ }
34
+
35
+ function __param(paramIndex, decorator) {
36
+ return function (target, key) { decorator(target, key, paramIndex); }
37
+ }
38
+
39
+ function __metadata(metadataKey, metadataValue) {
40
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
41
+ }
42
+
43
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
44
+ var e = new Error(message);
45
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
46
+ };
47
+
48
+ function jsonSchemaToZod(schema) {
49
+ const root = (schema ?? {});
50
+ const shape = {};
51
+ const required = new Set(root.required ?? []);
52
+ for (const [key, property] of Object.entries(root.properties ?? {})) {
53
+ let type = convert(property);
54
+ if (!required.has(key))
55
+ type = type.optional();
56
+ shape[key] = type;
57
+ }
58
+ return zod.z.object(shape);
59
+ }
60
+ function convert(schema) {
61
+ let type;
62
+ if (schema.enum) {
63
+ type = zod.z.enum(schema.enum.map(String));
64
+ }
65
+ else {
66
+ switch (schema.type) {
67
+ case "string":
68
+ type = zod.z.string();
69
+ break;
70
+ case "number":
71
+ type = zod.z.number();
72
+ break;
73
+ case "integer":
74
+ type = zod.z.number().int();
75
+ break;
76
+ case "boolean":
77
+ type = zod.z.boolean();
78
+ break;
79
+ case "array":
80
+ type = zod.z.array(schema.items ? convert(schema.items) : zod.z.any());
81
+ break;
82
+ case "object":
83
+ type = jsonSchemaToZod(schema);
84
+ break;
85
+ default:
86
+ type = zod.z.any();
87
+ }
88
+ }
89
+ return schema.description ? type.describe(schema.description) : type;
90
+ }
91
+
92
+ const MCP_OPTIONS = Symbol("adk:mcp-options");
93
+
94
+ var McpClient_1;
95
+ exports.McpClient = McpClient_1 = class McpClient extends core.ToolsetResolver {
96
+ constructor(options) {
97
+ super();
98
+ this.options = options;
99
+ this.logger = new common.Logger(McpClient_1.name);
100
+ this.catalogs = new Map();
101
+ }
102
+ async onModuleInit() {
103
+ for (const server of this.options.servers) {
104
+ try {
105
+ await this.connect(server);
106
+ }
107
+ catch (error) {
108
+ if (server.optional) {
109
+ this.logger.warn(`MCP server "${server.name}" unavailable (optional) — tools ignored.`);
110
+ continue;
111
+ }
112
+ throw new core.McpConnectionError(server.name, error);
113
+ }
114
+ }
115
+ }
116
+ async onModuleDestroy() {
117
+ for (const { client } of this.catalogs.values()) {
118
+ await client.close().catch(() => undefined);
119
+ }
120
+ }
121
+ async resolve(ref) {
122
+ const catalog = this.catalogs.get(ref.__adkToolset);
123
+ if (!catalog) {
124
+ const configured = this.options.servers.some((server) => server.name === ref.__adkToolset);
125
+ if (configured)
126
+ return [];
127
+ throw new core.McpConnectionError(ref.__adkToolset, new Error("server is not configured in McpModule.forRoot"));
128
+ }
129
+ const tools = ref.filter ? catalog.tools.filter((tool) => ref.filter?.includes(tool.name)) : catalog.tools;
130
+ return tools.map((tool) => ({
131
+ name: tool.name,
132
+ description: tool.description,
133
+ schema: jsonSchemaToZod(tool.inputSchema),
134
+ execute: async (input) => this.callTool(catalog.client, tool.name, input),
135
+ }));
136
+ }
137
+ async callTool(client, name, input) {
138
+ try {
139
+ const result = await client.callTool({ name, arguments: (input ?? {}) });
140
+ const texts = (result.content ?? [])
141
+ .filter((part) => part.type === "text")
142
+ .map((part) => part.text ?? "");
143
+ if (result.isError)
144
+ return { error: texts.join("\n") || `MCP tool "${name}" failed.` };
145
+ if (result.structuredContent)
146
+ return result.structuredContent;
147
+ return texts.length === 1 ? texts[0] : texts.join("\n");
148
+ }
149
+ catch (error) {
150
+ return { error: `MCP tool "${name}" failed: ${error instanceof Error ? error.message : String(error)}` };
151
+ }
152
+ }
153
+ async connect(server) {
154
+ const client = new index_js.Client({ name: "nestjs-adk", version: "1.0.0" });
155
+ await client.connect(this.createTransport(server.transport));
156
+ const { tools } = await client.listTools();
157
+ this.catalogs.set(server.name, {
158
+ client,
159
+ tools: tools.map((tool) => ({
160
+ name: tool.name,
161
+ description: tool.description ?? "",
162
+ inputSchema: tool.inputSchema,
163
+ })),
164
+ });
165
+ this.logger.log(`MCP server "${server.name}" connected — ${tools.length} tools in the catalog.`);
166
+ }
167
+ createTransport(transport) {
168
+ switch (transport.type) {
169
+ case "stdio":
170
+ return new stdio_js.StdioClientTransport({
171
+ command: transport.command,
172
+ args: transport.args,
173
+ env: transport.env ? { ...cleanEnv(process.env), ...transport.env } : undefined,
174
+ });
175
+ case "http":
176
+ return new streamableHttp_js.StreamableHTTPClientTransport(new URL(transport.url), {
177
+ requestInit: { headers: transport.headers },
178
+ });
179
+ case "sse":
180
+ return new sse_js.SSEClientTransport(new URL(transport.url), { requestInit: { headers: transport.headers } });
181
+ }
182
+ }
183
+ };
184
+ exports.McpClient = McpClient_1 = __decorate([
185
+ common.Injectable(),
186
+ __param(0, common.Inject(MCP_OPTIONS)),
187
+ __metadata("design:paramtypes", [Object])
188
+ ], exports.McpClient);
189
+ function cleanEnv(env) {
190
+ return Object.fromEntries(Object.entries(env).filter(([, value]) => value !== undefined));
191
+ }
192
+
193
+ var McpModule_1;
194
+ exports.McpModule = McpModule_1 = class McpModule {
195
+ static forRoot(options) {
196
+ return {
197
+ module: McpModule_1,
198
+ providers: [
199
+ { provide: MCP_OPTIONS, useValue: options },
200
+ exports.McpClient,
201
+ { provide: core.ToolsetResolver, useExisting: exports.McpClient },
202
+ ],
203
+ exports: [core.ToolsetResolver, exports.McpClient],
204
+ };
205
+ }
206
+ };
207
+ exports.McpModule = McpModule_1 = __decorate([
208
+ common.Global(),
209
+ common.Module({})
210
+ ], exports.McpModule);
211
+
212
+ const mcpTools = core.toolset;
213
+
214
+ exports.jsonSchemaToZod = jsonSchemaToZod;
215
+ exports.mcpTools = mcpTools;
@@ -0,0 +1,6 @@
1
+ import { toolset } from "@nestjs-adk/core";
2
+ export { McpClient } from "./lib/mcp-client";
3
+ export { McpModule } from "./lib/mcp.module";
4
+ export type { McpModuleOptions, McpServerConfig, McpTransportConfig } from "./lib/mcp-options";
5
+ export { jsonSchemaToZod } from "./lib/json-schema-to-zod";
6
+ export declare const mcpTools: typeof toolset;
package/dist/index.mjs ADDED
@@ -0,0 +1,212 @@
1
+ import { ToolsetResolver, McpConnectionError, toolset } from '@nestjs-adk/core';
2
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3
+ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
4
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
5
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
6
+ import { Injectable, Inject, Logger, Global, Module } from '@nestjs/common';
7
+ import { z } from 'zod';
8
+
9
+ /******************************************************************************
10
+ Copyright (c) Microsoft Corporation.
11
+
12
+ Permission to use, copy, modify, and/or distribute this software for any
13
+ purpose with or without fee is hereby granted.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
16
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
17
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
18
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
19
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
20
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
21
+ PERFORMANCE OF THIS SOFTWARE.
22
+ ***************************************************************************** */
23
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
24
+
25
+
26
+ function __decorate(decorators, target, key, desc) {
27
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
28
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
29
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
30
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
31
+ }
32
+
33
+ function __param(paramIndex, decorator) {
34
+ return function (target, key) { decorator(target, key, paramIndex); }
35
+ }
36
+
37
+ function __metadata(metadataKey, metadataValue) {
38
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
39
+ }
40
+
41
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
42
+ var e = new Error(message);
43
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
44
+ };
45
+
46
+ function jsonSchemaToZod(schema) {
47
+ const root = (schema ?? {});
48
+ const shape = {};
49
+ const required = new Set(root.required ?? []);
50
+ for (const [key, property] of Object.entries(root.properties ?? {})) {
51
+ let type = convert(property);
52
+ if (!required.has(key))
53
+ type = type.optional();
54
+ shape[key] = type;
55
+ }
56
+ return z.object(shape);
57
+ }
58
+ function convert(schema) {
59
+ let type;
60
+ if (schema.enum) {
61
+ type = z.enum(schema.enum.map(String));
62
+ }
63
+ else {
64
+ switch (schema.type) {
65
+ case "string":
66
+ type = z.string();
67
+ break;
68
+ case "number":
69
+ type = z.number();
70
+ break;
71
+ case "integer":
72
+ type = z.number().int();
73
+ break;
74
+ case "boolean":
75
+ type = z.boolean();
76
+ break;
77
+ case "array":
78
+ type = z.array(schema.items ? convert(schema.items) : z.any());
79
+ break;
80
+ case "object":
81
+ type = jsonSchemaToZod(schema);
82
+ break;
83
+ default:
84
+ type = z.any();
85
+ }
86
+ }
87
+ return schema.description ? type.describe(schema.description) : type;
88
+ }
89
+
90
+ const MCP_OPTIONS = Symbol("adk:mcp-options");
91
+
92
+ var McpClient_1;
93
+ let McpClient = McpClient_1 = class McpClient extends ToolsetResolver {
94
+ constructor(options) {
95
+ super();
96
+ this.options = options;
97
+ this.logger = new Logger(McpClient_1.name);
98
+ this.catalogs = new Map();
99
+ }
100
+ async onModuleInit() {
101
+ for (const server of this.options.servers) {
102
+ try {
103
+ await this.connect(server);
104
+ }
105
+ catch (error) {
106
+ if (server.optional) {
107
+ this.logger.warn(`MCP server "${server.name}" unavailable (optional) — tools ignored.`);
108
+ continue;
109
+ }
110
+ throw new McpConnectionError(server.name, error);
111
+ }
112
+ }
113
+ }
114
+ async onModuleDestroy() {
115
+ for (const { client } of this.catalogs.values()) {
116
+ await client.close().catch(() => undefined);
117
+ }
118
+ }
119
+ async resolve(ref) {
120
+ const catalog = this.catalogs.get(ref.__adkToolset);
121
+ if (!catalog) {
122
+ const configured = this.options.servers.some((server) => server.name === ref.__adkToolset);
123
+ if (configured)
124
+ return [];
125
+ throw new McpConnectionError(ref.__adkToolset, new Error("server is not configured in McpModule.forRoot"));
126
+ }
127
+ const tools = ref.filter ? catalog.tools.filter((tool) => ref.filter?.includes(tool.name)) : catalog.tools;
128
+ return tools.map((tool) => ({
129
+ name: tool.name,
130
+ description: tool.description,
131
+ schema: jsonSchemaToZod(tool.inputSchema),
132
+ execute: async (input) => this.callTool(catalog.client, tool.name, input),
133
+ }));
134
+ }
135
+ async callTool(client, name, input) {
136
+ try {
137
+ const result = await client.callTool({ name, arguments: (input ?? {}) });
138
+ const texts = (result.content ?? [])
139
+ .filter((part) => part.type === "text")
140
+ .map((part) => part.text ?? "");
141
+ if (result.isError)
142
+ return { error: texts.join("\n") || `MCP tool "${name}" failed.` };
143
+ if (result.structuredContent)
144
+ return result.structuredContent;
145
+ return texts.length === 1 ? texts[0] : texts.join("\n");
146
+ }
147
+ catch (error) {
148
+ return { error: `MCP tool "${name}" failed: ${error instanceof Error ? error.message : String(error)}` };
149
+ }
150
+ }
151
+ async connect(server) {
152
+ const client = new Client({ name: "nestjs-adk", version: "1.0.0" });
153
+ await client.connect(this.createTransport(server.transport));
154
+ const { tools } = await client.listTools();
155
+ this.catalogs.set(server.name, {
156
+ client,
157
+ tools: tools.map((tool) => ({
158
+ name: tool.name,
159
+ description: tool.description ?? "",
160
+ inputSchema: tool.inputSchema,
161
+ })),
162
+ });
163
+ this.logger.log(`MCP server "${server.name}" connected — ${tools.length} tools in the catalog.`);
164
+ }
165
+ createTransport(transport) {
166
+ switch (transport.type) {
167
+ case "stdio":
168
+ return new StdioClientTransport({
169
+ command: transport.command,
170
+ args: transport.args,
171
+ env: transport.env ? { ...cleanEnv(process.env), ...transport.env } : undefined,
172
+ });
173
+ case "http":
174
+ return new StreamableHTTPClientTransport(new URL(transport.url), {
175
+ requestInit: { headers: transport.headers },
176
+ });
177
+ case "sse":
178
+ return new SSEClientTransport(new URL(transport.url), { requestInit: { headers: transport.headers } });
179
+ }
180
+ }
181
+ };
182
+ McpClient = McpClient_1 = __decorate([
183
+ Injectable(),
184
+ __param(0, Inject(MCP_OPTIONS)),
185
+ __metadata("design:paramtypes", [Object])
186
+ ], McpClient);
187
+ function cleanEnv(env) {
188
+ return Object.fromEntries(Object.entries(env).filter(([, value]) => value !== undefined));
189
+ }
190
+
191
+ var McpModule_1;
192
+ let McpModule = McpModule_1 = class McpModule {
193
+ static forRoot(options) {
194
+ return {
195
+ module: McpModule_1,
196
+ providers: [
197
+ { provide: MCP_OPTIONS, useValue: options },
198
+ McpClient,
199
+ { provide: ToolsetResolver, useExisting: McpClient },
200
+ ],
201
+ exports: [ToolsetResolver, McpClient],
202
+ };
203
+ }
204
+ };
205
+ McpModule = McpModule_1 = __decorate([
206
+ Global(),
207
+ Module({})
208
+ ], McpModule);
209
+
210
+ const mcpTools = toolset;
211
+
212
+ export { McpClient, McpModule, jsonSchemaToZod, mcpTools };
@@ -0,0 +1,2 @@
1
+ import type { AnyZodObject } from "@nestjs-adk/core";
2
+ export declare function jsonSchemaToZod(schema: unknown): AnyZodObject;
@@ -0,0 +1,15 @@
1
+ import { type ResolvedTool, type ToolsetRef, ToolsetResolver } from "@nestjs-adk/core";
2
+ import { type OnModuleDestroy, type OnModuleInit } from "@nestjs/common";
3
+ import { type McpModuleOptions } from "./mcp-options";
4
+ export declare class McpClient extends ToolsetResolver implements OnModuleInit, OnModuleDestroy {
5
+ private readonly options;
6
+ private readonly logger;
7
+ private readonly catalogs;
8
+ constructor(options: McpModuleOptions);
9
+ onModuleInit(): Promise<void>;
10
+ onModuleDestroy(): Promise<void>;
11
+ resolve(ref: ToolsetRef): Promise<ResolvedTool[]>;
12
+ private callTool;
13
+ private connect;
14
+ private createTransport;
15
+ }
@@ -0,0 +1,23 @@
1
+ export type McpTransportConfig = {
2
+ type: "stdio";
3
+ command: string;
4
+ args?: string[];
5
+ env?: Record<string, string>;
6
+ } | {
7
+ type: "http";
8
+ url: string;
9
+ headers?: Record<string, string>;
10
+ } | {
11
+ type: "sse";
12
+ url: string;
13
+ headers?: Record<string, string>;
14
+ };
15
+ export interface McpServerConfig {
16
+ name: string;
17
+ transport: McpTransportConfig;
18
+ optional?: boolean;
19
+ }
20
+ export interface McpModuleOptions {
21
+ servers: McpServerConfig[];
22
+ }
23
+ export declare const MCP_OPTIONS: unique symbol;
@@ -0,0 +1,5 @@
1
+ import { type DynamicModule } from "@nestjs/common";
2
+ import { type McpModuleOptions } from "./mcp-options";
3
+ export declare class McpModule {
4
+ static forRoot(options: McpModuleOptions): DynamicModule;
5
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@nestjs-adk/mcp",
3
+ "version": "0.0.1",
4
+ "description": "MCP client integration for nestjs-adk: consume external MCP servers as agent tools.",
5
+ "license": "MIT",
6
+ "sideEffects": false,
7
+ "main": "dist/index.cjs",
8
+ "module": "dist/index.mjs",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.cjs"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "files": ["dist"],
19
+ "engines": {
20
+ "node": ">=20"
21
+ },
22
+ "scripts": {
23
+ "prebuild": "rimraf -g dist",
24
+ "build": "rollup -c"
25
+ },
26
+ "dependencies": {
27
+ "@modelcontextprotocol/sdk": "^1.12.0"
28
+ },
29
+ "peerDependencies": {
30
+ "@nestjs-adk/core": "^0.0.1",
31
+ "@nestjs/common": "^10.0.0 || ^11.0.0",
32
+ "@nestjs/core": "^10.0.0 || ^11.0.0",
33
+ "zod": "^3.23.0 || ^4.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@nestjs-adk/core": "^0.0.1"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }