@agiflowai/one-mcp 0.2.0

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.
@@ -0,0 +1,1211 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { CallToolRequestSchema, ListToolsRequestSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
3
+ import { readFile } from "node:fs/promises";
4
+ import { existsSync } from "node:fs";
5
+ import yaml from "js-yaml";
6
+ import { z } from "zod";
7
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
9
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
10
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
12
+ import express from "express";
13
+ import { randomUUID } from "node:crypto";
14
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
15
+
16
+ //#region src/utils/mcpConfigSchema.ts
17
+ /**
18
+ * mcpConfigSchema Utilities
19
+ *
20
+ * DESIGN PATTERNS:
21
+ * - Schema-based validation using Zod
22
+ * - Pure functions with no side effects
23
+ * - Type inference from schemas
24
+ * - Transformation from Claude Code format to internal format
25
+ *
26
+ * CODING STANDARDS:
27
+ * - Export individual functions and schemas
28
+ * - Use descriptive function names with verbs
29
+ * - Add JSDoc comments for complex logic
30
+ * - Keep functions small and focused
31
+ *
32
+ * AVOID:
33
+ * - Side effects (mutating external state)
34
+ * - Stateful logic (use services for state)
35
+ * - Loosely typed configs (use Zod for runtime safety)
36
+ */
37
+ /**
38
+ * Interpolate environment variables in a string
39
+ * Supports ${VAR_NAME} syntax
40
+ *
41
+ * This function replaces environment variable placeholders with their actual values.
42
+ * If an environment variable is not defined, the placeholder is kept as-is and a warning is logged.
43
+ *
44
+ * Examples:
45
+ * - "${HOME}/data" → "/Users/username/data"
46
+ * - "Bearer ${API_KEY}" → "Bearer sk-abc123xyz"
47
+ * - "${DATABASE_URL}/api" → "postgres://localhost:5432/mydb/api"
48
+ *
49
+ * Supported locations for environment variable interpolation:
50
+ * - Stdio config: command, args, env values
51
+ * - HTTP/SSE config: url, header values
52
+ *
53
+ * @param value - String that may contain environment variable references
54
+ * @returns String with environment variables replaced
55
+ */
56
+ function interpolateEnvVars(value) {
57
+ return value.replace(/\$\{([^}]+)\}/g, (_, varName) => {
58
+ const envValue = process.env[varName];
59
+ if (envValue === void 0) {
60
+ console.warn(`Environment variable ${varName} is not defined, keeping placeholder`);
61
+ return `\${${varName}}`;
62
+ }
63
+ return envValue;
64
+ });
65
+ }
66
+ /**
67
+ * Recursively interpolate environment variables in an object
68
+ *
69
+ * @param obj - Object that may contain environment variable references
70
+ * @returns Object with environment variables replaced
71
+ */
72
+ function interpolateEnvVarsInObject(obj) {
73
+ if (typeof obj === "string") return interpolateEnvVars(obj);
74
+ if (Array.isArray(obj)) return obj.map((item) => interpolateEnvVarsInObject(item));
75
+ if (obj !== null && typeof obj === "object") {
76
+ const result = {};
77
+ for (const [key, value] of Object.entries(obj)) result[key] = interpolateEnvVarsInObject(value);
78
+ return result;
79
+ }
80
+ return obj;
81
+ }
82
+ /**
83
+ * Claude Code / Claude Desktop standard MCP config format
84
+ * This is the format users write in their config files
85
+ */
86
+ const AdditionalConfigSchema = z.object({
87
+ instruction: z.string().optional(),
88
+ toolBlacklist: z.array(z.string()).optional(),
89
+ omitToolDescription: z.boolean().optional()
90
+ }).optional();
91
+ const ClaudeCodeStdioServerSchema = z.object({
92
+ command: z.string(),
93
+ args: z.array(z.string()).optional(),
94
+ env: z.record(z.string(), z.string()).optional(),
95
+ disabled: z.boolean().optional(),
96
+ instruction: z.string().optional(),
97
+ config: AdditionalConfigSchema
98
+ });
99
+ const ClaudeCodeHttpServerSchema = z.object({
100
+ url: z.string().url(),
101
+ headers: z.record(z.string(), z.string()).optional(),
102
+ type: z.enum(["http", "sse"]).optional(),
103
+ disabled: z.boolean().optional(),
104
+ instruction: z.string().optional(),
105
+ config: AdditionalConfigSchema
106
+ });
107
+ const ClaudeCodeServerConfigSchema = z.union([ClaudeCodeStdioServerSchema, ClaudeCodeHttpServerSchema]);
108
+ /**
109
+ * Full Claude Code MCP configuration schema
110
+ */
111
+ const ClaudeCodeMcpConfigSchema = z.object({ mcpServers: z.record(z.string(), ClaudeCodeServerConfigSchema) });
112
+ /**
113
+ * Internal MCP config format
114
+ * This is the normalized format used internally by the proxy
115
+ */
116
+ const McpStdioConfigSchema = z.object({
117
+ command: z.string(),
118
+ args: z.array(z.string()).optional(),
119
+ env: z.record(z.string(), z.string()).optional()
120
+ });
121
+ const McpHttpConfigSchema = z.object({
122
+ url: z.string().url(),
123
+ headers: z.record(z.string(), z.string()).optional()
124
+ });
125
+ const McpSseConfigSchema = z.object({
126
+ url: z.string().url(),
127
+ headers: z.record(z.string(), z.string()).optional()
128
+ });
129
+ const McpServerConfigSchema = z.discriminatedUnion("transport", [
130
+ z.object({
131
+ name: z.string(),
132
+ instruction: z.string().optional(),
133
+ toolBlacklist: z.array(z.string()).optional(),
134
+ omitToolDescription: z.boolean().optional(),
135
+ transport: z.literal("stdio"),
136
+ config: McpStdioConfigSchema
137
+ }),
138
+ z.object({
139
+ name: z.string(),
140
+ instruction: z.string().optional(),
141
+ toolBlacklist: z.array(z.string()).optional(),
142
+ omitToolDescription: z.boolean().optional(),
143
+ transport: z.literal("http"),
144
+ config: McpHttpConfigSchema
145
+ }),
146
+ z.object({
147
+ name: z.string(),
148
+ instruction: z.string().optional(),
149
+ toolBlacklist: z.array(z.string()).optional(),
150
+ omitToolDescription: z.boolean().optional(),
151
+ transport: z.literal("sse"),
152
+ config: McpSseConfigSchema
153
+ })
154
+ ]);
155
+ /**
156
+ * Full internal MCP configuration schema
157
+ */
158
+ const InternalMcpConfigSchema = z.object({ mcpServers: z.record(z.string(), McpServerConfigSchema) });
159
+ /**
160
+ * Transform Claude Code config to internal format
161
+ * Converts standard Claude Code MCP configuration to normalized internal format
162
+ *
163
+ * @param claudeConfig - Claude Code format configuration
164
+ * @returns Internal format configuration
165
+ */
166
+ function transformClaudeCodeConfig(claudeConfig) {
167
+ const transformedServers = {};
168
+ for (const [serverName, serverConfig] of Object.entries(claudeConfig.mcpServers)) {
169
+ if ("disabled" in serverConfig && serverConfig.disabled === true) continue;
170
+ if ("command" in serverConfig) {
171
+ const stdioConfig = serverConfig;
172
+ const interpolatedCommand = interpolateEnvVars(stdioConfig.command);
173
+ const interpolatedArgs = stdioConfig.args?.map((arg) => interpolateEnvVars(arg));
174
+ const interpolatedEnv = stdioConfig.env ? interpolateEnvVarsInObject(stdioConfig.env) : void 0;
175
+ transformedServers[serverName] = {
176
+ name: serverName,
177
+ instruction: stdioConfig.instruction || stdioConfig.config?.instruction,
178
+ toolBlacklist: stdioConfig.config?.toolBlacklist,
179
+ omitToolDescription: stdioConfig.config?.omitToolDescription,
180
+ transport: "stdio",
181
+ config: {
182
+ command: interpolatedCommand,
183
+ args: interpolatedArgs,
184
+ env: interpolatedEnv
185
+ }
186
+ };
187
+ } else if ("url" in serverConfig) {
188
+ const httpConfig = serverConfig;
189
+ const transport = httpConfig.type === "sse" ? "sse" : "http";
190
+ const interpolatedUrl = interpolateEnvVars(httpConfig.url);
191
+ const interpolatedHeaders = httpConfig.headers ? interpolateEnvVarsInObject(httpConfig.headers) : void 0;
192
+ transformedServers[serverName] = {
193
+ name: serverName,
194
+ instruction: httpConfig.instruction || httpConfig.config?.instruction,
195
+ toolBlacklist: httpConfig.config?.toolBlacklist,
196
+ omitToolDescription: httpConfig.config?.omitToolDescription,
197
+ transport,
198
+ config: {
199
+ url: interpolatedUrl,
200
+ headers: interpolatedHeaders
201
+ }
202
+ };
203
+ }
204
+ }
205
+ return { mcpServers: transformedServers };
206
+ }
207
+ /**
208
+ * Parse and validate MCP config from raw JSON
209
+ * Validates against Claude Code format, transforms to internal format, and validates result
210
+ *
211
+ * @param rawConfig - Raw JSON configuration object
212
+ * @returns Validated and transformed internal configuration
213
+ * @throws ZodError if validation fails
214
+ */
215
+ function parseMcpConfig(rawConfig) {
216
+ const internalConfig = transformClaudeCodeConfig(ClaudeCodeMcpConfigSchema.parse(rawConfig));
217
+ return InternalMcpConfigSchema.parse(internalConfig);
218
+ }
219
+
220
+ //#endregion
221
+ //#region src/services/ConfigFetcherService.ts
222
+ /**
223
+ * ConfigFetcherService
224
+ *
225
+ * DESIGN PATTERNS:
226
+ * - Service pattern for business logic encapsulation
227
+ * - Single responsibility principle
228
+ * - Caching pattern for performance optimization
229
+ *
230
+ * CODING STANDARDS:
231
+ * - Use async/await for asynchronous operations
232
+ * - Throw descriptive errors for error cases
233
+ * - Keep methods focused and well-named
234
+ * - Document complex logic with comments
235
+ *
236
+ * AVOID:
237
+ * - Mixing concerns (keep focused on single domain)
238
+ * - Direct tool implementation (services should be tool-agnostic)
239
+ */
240
+ /**
241
+ * Service for fetching and caching MCP server configurations from local file
242
+ */
243
+ var ConfigFetcherService = class {
244
+ configFilePath;
245
+ cacheTtlMs;
246
+ cachedConfig = null;
247
+ lastFetchTime = 0;
248
+ constructor(options) {
249
+ this.configFilePath = options.configFilePath;
250
+ this.cacheTtlMs = options.cacheTtlMs || 6e4;
251
+ if (!this.configFilePath) throw new Error("configFilePath must be provided");
252
+ }
253
+ /**
254
+ * Fetch MCP configuration from local file with caching
255
+ * @param forceRefresh - Force reload from source, bypassing cache
256
+ */
257
+ async fetchConfiguration(forceRefresh = false) {
258
+ const now = Date.now();
259
+ if (!forceRefresh && this.cachedConfig && now - this.lastFetchTime < this.cacheTtlMs) return this.cachedConfig;
260
+ const config = await this.loadFromFile();
261
+ if (!config.mcpServers || typeof config.mcpServers !== "object") throw new Error("Invalid MCP configuration: missing or invalid mcpServers");
262
+ this.cachedConfig = config;
263
+ this.lastFetchTime = now;
264
+ return config;
265
+ }
266
+ /**
267
+ * Load configuration from a local file (supports JSON and YAML)
268
+ */
269
+ async loadFromFile() {
270
+ if (!this.configFilePath) throw new Error("No config file path provided");
271
+ if (!existsSync(this.configFilePath)) throw new Error(`Config file not found: ${this.configFilePath}`);
272
+ try {
273
+ const content = await readFile(this.configFilePath, "utf-8");
274
+ let rawConfig;
275
+ if (this.configFilePath.endsWith(".yaml") || this.configFilePath.endsWith(".yml")) rawConfig = yaml.load(content);
276
+ else rawConfig = JSON.parse(content);
277
+ return parseMcpConfig(rawConfig);
278
+ } catch (error) {
279
+ if (error instanceof Error) throw new Error(`Failed to load config file: ${error.message}`);
280
+ throw new Error("Failed to load config file: Unknown error");
281
+ }
282
+ }
283
+ /**
284
+ * Clear the cached configuration
285
+ */
286
+ clearCache() {
287
+ this.cachedConfig = null;
288
+ this.lastFetchTime = 0;
289
+ }
290
+ /**
291
+ * Check if cache is valid
292
+ */
293
+ isCacheValid() {
294
+ const now = Date.now();
295
+ return this.cachedConfig !== null && now - this.lastFetchTime < this.cacheTtlMs;
296
+ }
297
+ };
298
+
299
+ //#endregion
300
+ //#region src/services/McpClientManagerService.ts
301
+ /**
302
+ * MCP Client wrapper for managing individual server connections
303
+ */
304
+ var McpClient = class {
305
+ serverName;
306
+ serverInstruction;
307
+ toolBlacklist;
308
+ omitToolDescription;
309
+ transport;
310
+ client;
311
+ childProcess;
312
+ connected = false;
313
+ constructor(serverName, transport, client, config) {
314
+ this.serverName = serverName;
315
+ this.serverInstruction = config.instruction;
316
+ this.toolBlacklist = config.toolBlacklist;
317
+ this.omitToolDescription = config.omitToolDescription;
318
+ this.transport = transport;
319
+ this.client = client;
320
+ }
321
+ setChildProcess(process$1) {
322
+ this.childProcess = process$1;
323
+ }
324
+ setConnected(connected) {
325
+ this.connected = connected;
326
+ }
327
+ async listTools() {
328
+ if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
329
+ return (await this.client.listTools()).tools;
330
+ }
331
+ async listResources() {
332
+ if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
333
+ return (await this.client.listResources()).resources;
334
+ }
335
+ async listPrompts() {
336
+ if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
337
+ return (await this.client.listPrompts()).prompts;
338
+ }
339
+ async callTool(name, args) {
340
+ if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
341
+ return await this.client.callTool({
342
+ name,
343
+ arguments: args
344
+ });
345
+ }
346
+ async readResource(uri) {
347
+ if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
348
+ return await this.client.readResource({ uri });
349
+ }
350
+ async getPrompt(name, args) {
351
+ if (!this.connected) throw new Error(`Client for ${this.serverName} is not connected`);
352
+ return await this.client.getPrompt({
353
+ name,
354
+ arguments: args
355
+ });
356
+ }
357
+ async close() {
358
+ if (this.childProcess) this.childProcess.kill();
359
+ await this.client.close();
360
+ this.connected = false;
361
+ }
362
+ };
363
+ /**
364
+ * Service for managing MCP client connections to remote servers
365
+ */
366
+ var McpClientManagerService = class {
367
+ clients = /* @__PURE__ */ new Map();
368
+ constructor() {
369
+ process.on("exit", () => {
370
+ this.cleanupOnExit();
371
+ });
372
+ process.on("SIGINT", () => {
373
+ this.cleanupOnExit();
374
+ process.exit(0);
375
+ });
376
+ process.on("SIGTERM", () => {
377
+ this.cleanupOnExit();
378
+ process.exit(0);
379
+ });
380
+ }
381
+ /**
382
+ * Cleanup all resources on exit (child processes)
383
+ */
384
+ cleanupOnExit() {
385
+ for (const [serverName, client] of this.clients) try {
386
+ const childProcess = client["childProcess"];
387
+ if (childProcess && !childProcess.killed) {
388
+ console.error(`Killing stdio MCP server: ${serverName} (PID: ${childProcess.pid})`);
389
+ childProcess.kill("SIGTERM");
390
+ setTimeout(() => {
391
+ if (!childProcess.killed) {
392
+ console.error(`Force killing stdio MCP server: ${serverName} (PID: ${childProcess.pid})`);
393
+ childProcess.kill("SIGKILL");
394
+ }
395
+ }, 1e3);
396
+ }
397
+ } catch (error) {
398
+ console.error(`Failed to kill child process for ${serverName}:`, error);
399
+ }
400
+ }
401
+ /**
402
+ * Connect to an MCP server based on its configuration with timeout
403
+ */
404
+ async connectToServer(serverName, config, timeoutMs = 1e4) {
405
+ if (this.clients.has(serverName)) throw new Error(`Client for ${serverName} is already connected`);
406
+ const client = new Client({
407
+ name: `@agiflowai/one-mcp-client`,
408
+ version: "0.1.0"
409
+ }, { capabilities: {} });
410
+ const mcpClient = new McpClient(serverName, config.transport, client, {
411
+ instruction: config.instruction,
412
+ toolBlacklist: config.toolBlacklist,
413
+ omitToolDescription: config.omitToolDescription
414
+ });
415
+ try {
416
+ await Promise.race([this.performConnection(mcpClient, config), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`Connection timeout after ${timeoutMs}ms`)), timeoutMs))]);
417
+ mcpClient.setConnected(true);
418
+ if (!mcpClient.serverInstruction) try {
419
+ const serverInstruction = mcpClient["client"].getInstructions();
420
+ if (serverInstruction) mcpClient.serverInstruction = serverInstruction;
421
+ } catch (error) {
422
+ console.error(`Failed to get server instruction from ${serverName}:`, error);
423
+ }
424
+ this.clients.set(serverName, mcpClient);
425
+ } catch (error) {
426
+ await mcpClient.close();
427
+ throw error;
428
+ }
429
+ }
430
+ /**
431
+ * Perform the actual connection to MCP server
432
+ */
433
+ async performConnection(mcpClient, config) {
434
+ if (config.transport === "stdio") await this.connectStdioClient(mcpClient, config.config);
435
+ else if (config.transport === "sse") await this.connectSseClient(mcpClient, config.config);
436
+ else throw new Error(`Unsupported transport type: ${config.transport}`);
437
+ }
438
+ async connectStdioClient(mcpClient, config) {
439
+ const transport = new StdioClientTransport({
440
+ command: config.command,
441
+ args: config.args,
442
+ env: config.env
443
+ });
444
+ await mcpClient["client"].connect(transport);
445
+ const childProcess = transport["_process"];
446
+ if (childProcess) mcpClient.setChildProcess(childProcess);
447
+ }
448
+ async connectSseClient(mcpClient, config) {
449
+ const transport = new SSEClientTransport(new URL(config.url));
450
+ await mcpClient["client"].connect(transport);
451
+ }
452
+ /**
453
+ * Get a connected client by server name
454
+ */
455
+ getClient(serverName) {
456
+ return this.clients.get(serverName);
457
+ }
458
+ /**
459
+ * Get all connected clients
460
+ */
461
+ getAllClients() {
462
+ return Array.from(this.clients.values());
463
+ }
464
+ /**
465
+ * Disconnect from a specific server
466
+ */
467
+ async disconnectServer(serverName) {
468
+ const client = this.clients.get(serverName);
469
+ if (client) {
470
+ await client.close();
471
+ this.clients.delete(serverName);
472
+ }
473
+ }
474
+ /**
475
+ * Disconnect from all servers
476
+ */
477
+ async disconnectAll() {
478
+ const disconnectPromises = Array.from(this.clients.values()).map((client) => client.close());
479
+ await Promise.all(disconnectPromises);
480
+ this.clients.clear();
481
+ }
482
+ /**
483
+ * Check if a server is connected
484
+ */
485
+ isConnected(serverName) {
486
+ return this.clients.has(serverName);
487
+ }
488
+ };
489
+
490
+ //#endregion
491
+ //#region src/tools/DescribeToolsTool.ts
492
+ var DescribeToolsTool = class DescribeToolsTool {
493
+ static TOOL_NAME = "describe_tools";
494
+ clientManager;
495
+ constructor(clientManager) {
496
+ this.clientManager = clientManager;
497
+ }
498
+ async getDefinition() {
499
+ const clients = this.clientManager.getAllClients();
500
+ const serverDescriptions = await Promise.all(clients.map(async (client) => {
501
+ try {
502
+ const tools = await client.listTools();
503
+ const blacklist = new Set(client.toolBlacklist || []);
504
+ const filteredTools = tools.filter((t) => !blacklist.has(t.name));
505
+ const toolList = client.omitToolDescription ? filteredTools.map((t) => t.name).join(", ") : filteredTools.map((t) => `${t.name}: ${t.description || "No description"}`).join("\n");
506
+ const instructionLine = client.serverInstruction ? `\n- Description: ${client.serverInstruction}` : "";
507
+ return `\n\n### Server: ${client.serverName}${instructionLine}\n- Available tools:${toolList ? "\n" + toolList : ""}`;
508
+ } catch (error) {
509
+ console.error(`Failed to list tools from ${client.serverName}:`, error);
510
+ const instructionLine = client.serverInstruction ? `\n- Description: ${client.serverInstruction}` : "";
511
+ return `\n\n**Server: ${client.serverName}**${instructionLine}\n`;
512
+ }
513
+ }));
514
+ return {
515
+ name: DescribeToolsTool.TOOL_NAME,
516
+ description: `Learn how to use multiple MCP tools before using them. Below are supported tools and capabilities.
517
+
518
+ ## Available MCP Servers:${serverDescriptions.join("")}
519
+
520
+ ## Usage:
521
+ You MUST call this tool with a list of tool names to learn how to use them properly before use_tool; this includes:
522
+ - Arguments schema needed to pass to the tool use
523
+ - Description about each tool
524
+
525
+ This tool is optimized for batch queries - you can request multiple tools at once for better performance.`,
526
+ inputSchema: {
527
+ type: "object",
528
+ properties: {
529
+ toolNames: {
530
+ type: "array",
531
+ items: { type: "string" },
532
+ description: "List of tool names to get detailed information about",
533
+ minItems: 1
534
+ },
535
+ serverName: {
536
+ type: "string",
537
+ description: "Optional server name to search within. If not specified, searches all servers."
538
+ }
539
+ },
540
+ required: ["toolNames"],
541
+ additionalProperties: false
542
+ }
543
+ };
544
+ }
545
+ async execute(input) {
546
+ try {
547
+ const { toolNames, serverName } = input;
548
+ const clients = this.clientManager.getAllClients();
549
+ if (!toolNames || toolNames.length === 0) return {
550
+ content: [{
551
+ type: "text",
552
+ text: "No tool names provided. Please specify at least one tool name."
553
+ }],
554
+ isError: true
555
+ };
556
+ if (serverName) {
557
+ const client = this.clientManager.getClient(serverName);
558
+ if (!client) return {
559
+ content: [{
560
+ type: "text",
561
+ text: `Server "${serverName}" not found. Available servers: ${clients.map((c) => c.serverName).join(", ")}`
562
+ }],
563
+ isError: true
564
+ };
565
+ const tools = await client.listTools();
566
+ const blacklist = new Set(client.toolBlacklist || []);
567
+ const filteredTools = tools.filter((t) => !blacklist.has(t.name));
568
+ const foundTools$1 = [];
569
+ const notFoundTools$1 = [];
570
+ for (const toolName of toolNames) {
571
+ if (blacklist.has(toolName)) {
572
+ notFoundTools$1.push(toolName);
573
+ continue;
574
+ }
575
+ const tool = filteredTools.find((t) => t.name === toolName);
576
+ if (tool) foundTools$1.push({
577
+ server: serverName,
578
+ tool: {
579
+ name: tool.name,
580
+ description: tool.description,
581
+ inputSchema: tool.inputSchema
582
+ }
583
+ });
584
+ else notFoundTools$1.push(toolName);
585
+ }
586
+ if (foundTools$1.length === 0) return {
587
+ content: [{
588
+ type: "text",
589
+ text: `None of the requested tools found on server "${serverName}".\nRequested: ${toolNames.join(", ")}\nAvailable tools: ${tools.map((t) => t.name).join(", ")}`
590
+ }],
591
+ isError: true
592
+ };
593
+ const result$1 = { tools: foundTools$1 };
594
+ if (notFoundTools$1.length > 0) {
595
+ result$1.notFound = notFoundTools$1;
596
+ result$1.warning = `Some tools were not found on server "${serverName}": ${notFoundTools$1.join(", ")}`;
597
+ }
598
+ return { content: [{
599
+ type: "text",
600
+ text: JSON.stringify(result$1, null, 2)
601
+ }] };
602
+ }
603
+ const foundTools = [];
604
+ const notFoundTools = [...toolNames];
605
+ const toolMatches = /* @__PURE__ */ new Map();
606
+ const results = await Promise.all(clients.map(async (client) => {
607
+ try {
608
+ const tools = await client.listTools();
609
+ const blacklist = new Set(client.toolBlacklist || []);
610
+ const filteredTools = tools.filter((t) => !blacklist.has(t.name));
611
+ const matches = [];
612
+ for (const toolName of toolNames) {
613
+ if (blacklist.has(toolName)) continue;
614
+ const tool = filteredTools.find((t) => t.name === toolName);
615
+ if (tool) matches.push({
616
+ toolName,
617
+ server: client.serverName,
618
+ tool
619
+ });
620
+ }
621
+ return matches;
622
+ } catch (error) {
623
+ console.error(`Failed to list tools from ${client.serverName}:`, error);
624
+ return [];
625
+ }
626
+ }));
627
+ for (const matches of results) for (const match of matches) {
628
+ if (!toolMatches.has(match.toolName)) toolMatches.set(match.toolName, []);
629
+ toolMatches.get(match.toolName).push({
630
+ server: match.server,
631
+ tool: match.tool
632
+ });
633
+ }
634
+ const ambiguousTools = [];
635
+ for (const toolName of toolNames) {
636
+ const matches = toolMatches.get(toolName);
637
+ if (!matches || matches.length === 0) continue;
638
+ if (matches.length === 1) {
639
+ const match = matches[0];
640
+ foundTools.push({
641
+ server: match.server,
642
+ tool: {
643
+ name: match.tool.name,
644
+ description: match.tool.description,
645
+ inputSchema: match.tool.inputSchema
646
+ }
647
+ });
648
+ const idx = notFoundTools.indexOf(toolName);
649
+ if (idx > -1) notFoundTools.splice(idx, 1);
650
+ } else {
651
+ ambiguousTools.push({
652
+ toolName,
653
+ servers: matches.map((m) => m.server)
654
+ });
655
+ const idx = notFoundTools.indexOf(toolName);
656
+ if (idx > -1) notFoundTools.splice(idx, 1);
657
+ }
658
+ }
659
+ if (foundTools.length === 0 && ambiguousTools.length === 0) return {
660
+ content: [{
661
+ type: "text",
662
+ text: `None of the requested tools found on any connected server.\nRequested: ${toolNames.join(", ")}\nUse describe_tools without arguments to see available servers.`
663
+ }],
664
+ isError: true
665
+ };
666
+ const result = { tools: foundTools };
667
+ if (notFoundTools.length > 0) result.notFound = notFoundTools;
668
+ if (ambiguousTools.length > 0) result.ambiguous = ambiguousTools.map((item) => ({
669
+ toolName: item.toolName,
670
+ servers: item.servers,
671
+ message: `Tool "${item.toolName}" found on multiple servers: ${item.servers.join(", ")}. Please specify serverName to disambiguate.`
672
+ }));
673
+ const warnings = [];
674
+ if (notFoundTools.length > 0) warnings.push(`Tools not found: ${notFoundTools.join(", ")}`);
675
+ if (ambiguousTools.length > 0) warnings.push(`Ambiguous tools (specify serverName): ${ambiguousTools.map((t) => t.toolName).join(", ")}`);
676
+ if (warnings.length > 0) result.warnings = warnings;
677
+ return { content: [{
678
+ type: "text",
679
+ text: JSON.stringify(result, null, 2)
680
+ }] };
681
+ } catch (error) {
682
+ return {
683
+ content: [{
684
+ type: "text",
685
+ text: `Error describing tools: ${error instanceof Error ? error.message : "Unknown error"}`
686
+ }],
687
+ isError: true
688
+ };
689
+ }
690
+ }
691
+ };
692
+
693
+ //#endregion
694
+ //#region src/tools/UseToolTool.ts
695
+ var UseToolTool = class UseToolTool {
696
+ static TOOL_NAME = "use_tool";
697
+ clientManager;
698
+ constructor(clientManager) {
699
+ this.clientManager = clientManager;
700
+ }
701
+ getDefinition() {
702
+ return {
703
+ name: UseToolTool.TOOL_NAME,
704
+ description: `Execute an MCP tool with provided arguments. You MUST call describe_tools first to discover the tool's correct arguments. Then to use tool:
705
+ - Provide toolName and toolArgs based on the schema
706
+ - If multiple servers provide the same tool, specify serverName
707
+ `,
708
+ inputSchema: {
709
+ type: "object",
710
+ properties: {
711
+ toolName: {
712
+ type: "string",
713
+ description: "Name of the tool to execute"
714
+ },
715
+ toolArgs: {
716
+ type: "object",
717
+ description: "Arguments to pass to the tool, as discovered from describe_tools"
718
+ },
719
+ serverName: {
720
+ type: "string",
721
+ description: "Optional server name to disambiguate when multiple servers have the same tool"
722
+ }
723
+ },
724
+ required: ["toolName"],
725
+ additionalProperties: false
726
+ }
727
+ };
728
+ }
729
+ async execute(input) {
730
+ try {
731
+ const { toolName, toolArgs = {}, serverName } = input;
732
+ const clients = this.clientManager.getAllClients();
733
+ if (serverName) {
734
+ const client$1 = this.clientManager.getClient(serverName);
735
+ if (!client$1) return {
736
+ content: [{
737
+ type: "text",
738
+ text: `Server "${serverName}" not found. Available servers: ${clients.map((c) => c.serverName).join(", ")}`
739
+ }],
740
+ isError: true
741
+ };
742
+ if (client$1.toolBlacklist && client$1.toolBlacklist.includes(toolName)) return {
743
+ content: [{
744
+ type: "text",
745
+ text: `Tool "${toolName}" is blacklisted on server "${serverName}" and cannot be executed.`
746
+ }],
747
+ isError: true
748
+ };
749
+ try {
750
+ return await client$1.callTool(toolName, toolArgs);
751
+ } catch (error) {
752
+ return {
753
+ content: [{
754
+ type: "text",
755
+ text: `Failed to call tool "${toolName}" on server "${serverName}": ${error instanceof Error ? error.message : "Unknown error"}`
756
+ }],
757
+ isError: true
758
+ };
759
+ }
760
+ }
761
+ const matchingServers = [];
762
+ const results = await Promise.all(clients.map(async (client$1) => {
763
+ try {
764
+ if (client$1.toolBlacklist && client$1.toolBlacklist.includes(toolName)) return null;
765
+ if ((await client$1.listTools()).some((t) => t.name === toolName)) return client$1.serverName;
766
+ } catch (error) {
767
+ console.error(`Failed to list tools from ${client$1.serverName}:`, error);
768
+ }
769
+ return null;
770
+ }));
771
+ matchingServers.push(...results.filter((r) => r !== null));
772
+ if (matchingServers.length === 0) return {
773
+ content: [{
774
+ type: "text",
775
+ text: `Tool "${toolName}" not found on any connected server. Use describe_tools to see available tools.`
776
+ }],
777
+ isError: true
778
+ };
779
+ if (matchingServers.length > 1) return {
780
+ content: [{
781
+ type: "text",
782
+ text: `Multiple servers provide tool "${toolName}". Please specify serverName. Available servers: ${matchingServers.join(", ")}`
783
+ }],
784
+ isError: true
785
+ };
786
+ const targetServerName = matchingServers[0];
787
+ const client = this.clientManager.getClient(targetServerName);
788
+ if (!client) return {
789
+ content: [{
790
+ type: "text",
791
+ text: `Internal error: Server "${targetServerName}" was found but is not connected`
792
+ }],
793
+ isError: true
794
+ };
795
+ try {
796
+ return await client.callTool(toolName, toolArgs);
797
+ } catch (error) {
798
+ return {
799
+ content: [{
800
+ type: "text",
801
+ text: `Failed to call tool "${toolName}": ${error instanceof Error ? error.message : "Unknown error"}`
802
+ }],
803
+ isError: true
804
+ };
805
+ }
806
+ } catch (error) {
807
+ return {
808
+ content: [{
809
+ type: "text",
810
+ text: `Error executing tool: ${error instanceof Error ? error.message : "Unknown error"}`
811
+ }],
812
+ isError: true
813
+ };
814
+ }
815
+ }
816
+ };
817
+
818
+ //#endregion
819
+ //#region src/server/index.ts
820
+ /**
821
+ * MCP Server Setup
822
+ *
823
+ * DESIGN PATTERNS:
824
+ * - Factory pattern for server creation
825
+ * - Tool registration pattern
826
+ * - Dependency injection for services
827
+ *
828
+ * CODING STANDARDS:
829
+ * - Register all tools, resources, and prompts here
830
+ * - Keep server setup modular and extensible
831
+ * - Import tools from ../tools/ and register them in the handlers
832
+ */
833
+ async function createServer(options) {
834
+ const server = new Server({
835
+ name: "@agiflowai/one-mcp",
836
+ version: "0.1.0"
837
+ }, { capabilities: { tools: {} } });
838
+ const clientManager = new McpClientManagerService();
839
+ if (options?.configFilePath) try {
840
+ const config = await new ConfigFetcherService({ configFilePath: options.configFilePath }).fetchConfiguration(options.noCache || false);
841
+ const connectionPromises = Object.entries(config.mcpServers).map(async ([serverName, serverConfig]) => {
842
+ try {
843
+ await clientManager.connectToServer(serverName, serverConfig);
844
+ console.error(`Connected to MCP server: ${serverName}`);
845
+ } catch (error) {
846
+ console.error(`Failed to connect to ${serverName}:`, error);
847
+ }
848
+ });
849
+ await Promise.all(connectionPromises);
850
+ } catch (error) {
851
+ console.error("Failed to load MCP configuration:", error);
852
+ }
853
+ const describeTools = new DescribeToolsTool(clientManager);
854
+ const useTool = new UseToolTool(clientManager);
855
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [await describeTools.getDefinition(), useTool.getDefinition()] }));
856
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
857
+ const { name, arguments: args } = request.params;
858
+ if (name === DescribeToolsTool.TOOL_NAME) return await describeTools.execute(args);
859
+ if (name === UseToolTool.TOOL_NAME) return await useTool.execute(args);
860
+ throw new Error(`Unknown tool: ${name}`);
861
+ });
862
+ return server;
863
+ }
864
+
865
+ //#endregion
866
+ //#region src/transports/stdio.ts
867
+ /**
868
+ * Stdio transport handler for MCP server
869
+ * Used for command-line and direct integrations
870
+ */
871
+ var StdioTransportHandler = class {
872
+ server;
873
+ transport = null;
874
+ constructor(server) {
875
+ this.server = server;
876
+ }
877
+ async start() {
878
+ this.transport = new StdioServerTransport();
879
+ await this.server.connect(this.transport);
880
+ console.error("@agiflowai/one-mcp MCP server started on stdio");
881
+ }
882
+ async stop() {
883
+ if (this.transport) {
884
+ await this.transport.close();
885
+ this.transport = null;
886
+ }
887
+ }
888
+ };
889
+
890
+ //#endregion
891
+ //#region src/transports/sse.ts
892
+ /**
893
+ * Session manager for SSE transports
894
+ */
895
+ var SseSessionManager = class {
896
+ sessions = /* @__PURE__ */ new Map();
897
+ getSession(sessionId) {
898
+ return this.sessions.get(sessionId)?.transport;
899
+ }
900
+ setSession(sessionId, transport, server) {
901
+ this.sessions.set(sessionId, {
902
+ transport,
903
+ server
904
+ });
905
+ }
906
+ deleteSession(sessionId) {
907
+ const session = this.sessions.get(sessionId);
908
+ if (session) session.server.close();
909
+ this.sessions.delete(sessionId);
910
+ }
911
+ hasSession(sessionId) {
912
+ return this.sessions.has(sessionId);
913
+ }
914
+ clear() {
915
+ for (const session of this.sessions.values()) session.server.close();
916
+ this.sessions.clear();
917
+ }
918
+ };
919
+ /**
920
+ * SSE (Server-Sent Events) transport handler
921
+ * Legacy transport for backwards compatibility (protocol version 2024-11-05)
922
+ * Uses separate endpoints: /sse for SSE stream (GET) and /messages for client messages (POST)
923
+ */
924
+ var SseTransportHandler = class {
925
+ serverFactory;
926
+ app;
927
+ server = null;
928
+ sessionManager;
929
+ config;
930
+ constructor(serverFactory, config) {
931
+ this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
932
+ this.app = express();
933
+ this.sessionManager = new SseSessionManager();
934
+ this.config = {
935
+ mode: config.mode,
936
+ port: config.port ?? 3e3,
937
+ host: config.host ?? "localhost"
938
+ };
939
+ this.setupMiddleware();
940
+ this.setupRoutes();
941
+ }
942
+ setupMiddleware() {
943
+ this.app.use(express.json());
944
+ }
945
+ setupRoutes() {
946
+ this.app.get("/sse", async (req, res) => {
947
+ await this.handleSseConnection(req, res);
948
+ });
949
+ this.app.post("/messages", async (req, res) => {
950
+ await this.handlePostMessage(req, res);
951
+ });
952
+ this.app.get("/health", (_req, res) => {
953
+ res.json({
954
+ status: "ok",
955
+ transport: "sse"
956
+ });
957
+ });
958
+ }
959
+ async handleSseConnection(_req, res) {
960
+ try {
961
+ const mcpServer = this.serverFactory();
962
+ const transport = new SSEServerTransport("/messages", res);
963
+ this.sessionManager.setSession(transport.sessionId, transport, mcpServer);
964
+ res.on("close", () => {
965
+ this.sessionManager.deleteSession(transport.sessionId);
966
+ });
967
+ await mcpServer.connect(transport);
968
+ console.error(`SSE session established: ${transport.sessionId}`);
969
+ } catch (error) {
970
+ console.error("Error handling SSE connection:", error);
971
+ if (!res.headersSent) res.status(500).send("Internal Server Error");
972
+ }
973
+ }
974
+ async handlePostMessage(req, res) {
975
+ const sessionId = req.query.sessionId;
976
+ if (!sessionId) {
977
+ res.status(400).send("Missing sessionId query parameter");
978
+ return;
979
+ }
980
+ const transport = this.sessionManager.getSession(sessionId);
981
+ if (!transport) {
982
+ res.status(404).send("No transport found for sessionId");
983
+ return;
984
+ }
985
+ try {
986
+ await transport.handlePostMessage(req, res, req.body);
987
+ } catch (error) {
988
+ console.error("Error handling post message:", error);
989
+ if (!res.headersSent) res.status(500).send("Internal Server Error");
990
+ }
991
+ }
992
+ async start() {
993
+ return new Promise((resolve, reject) => {
994
+ try {
995
+ this.server = this.app.listen(this.config.port, this.config.host, () => {
996
+ console.error(`@agiflowai/one-mcp MCP server started with SSE transport on http://${this.config.host}:${this.config.port}`);
997
+ console.error(`SSE endpoint: http://${this.config.host}:${this.config.port}/sse`);
998
+ console.error(`Messages endpoint: http://${this.config.host}:${this.config.port}/messages`);
999
+ console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
1000
+ resolve();
1001
+ });
1002
+ this.server.on("error", (error) => {
1003
+ reject(error);
1004
+ });
1005
+ } catch (error) {
1006
+ reject(error);
1007
+ }
1008
+ });
1009
+ }
1010
+ async stop() {
1011
+ return new Promise((resolve, reject) => {
1012
+ if (this.server) {
1013
+ this.sessionManager.clear();
1014
+ this.server.close((err) => {
1015
+ if (err) reject(err);
1016
+ else {
1017
+ this.server = null;
1018
+ resolve();
1019
+ }
1020
+ });
1021
+ } else resolve();
1022
+ });
1023
+ }
1024
+ getPort() {
1025
+ return this.config.port;
1026
+ }
1027
+ getHost() {
1028
+ return this.config.host;
1029
+ }
1030
+ };
1031
+
1032
+ //#endregion
1033
+ //#region src/transports/http.ts
1034
+ /**
1035
+ * HTTP Transport Handler
1036
+ *
1037
+ * DESIGN PATTERNS:
1038
+ * - Transport handler pattern implementing TransportHandler interface
1039
+ * - Session management for stateful connections
1040
+ * - Streamable HTTP protocol (2025-03-26) with resumability support
1041
+ * - Factory pattern for creating MCP server instances per session
1042
+ *
1043
+ * CODING STANDARDS:
1044
+ * - Use async/await for all asynchronous operations
1045
+ * - Implement proper session lifecycle management
1046
+ * - Handle errors gracefully with appropriate HTTP status codes
1047
+ * - Provide health check endpoint for monitoring
1048
+ * - Clean up resources on shutdown
1049
+ *
1050
+ * AVOID:
1051
+ * - Sharing MCP server instances across sessions (use factory pattern)
1052
+ * - Forgetting to clean up sessions on disconnect
1053
+ * - Missing error handling for request processing
1054
+ * - Hardcoded configuration (use TransportConfig)
1055
+ */
1056
+ /**
1057
+ * HTTP session manager
1058
+ */
1059
+ var HttpFullSessionManager = class {
1060
+ sessions = /* @__PURE__ */ new Map();
1061
+ getSession(sessionId) {
1062
+ return this.sessions.get(sessionId);
1063
+ }
1064
+ setSession(sessionId, transport, server) {
1065
+ this.sessions.set(sessionId, {
1066
+ transport,
1067
+ server
1068
+ });
1069
+ }
1070
+ deleteSession(sessionId) {
1071
+ const session = this.sessions.get(sessionId);
1072
+ if (session) session.server.close();
1073
+ this.sessions.delete(sessionId);
1074
+ }
1075
+ hasSession(sessionId) {
1076
+ return this.sessions.has(sessionId);
1077
+ }
1078
+ clear() {
1079
+ for (const session of this.sessions.values()) session.server.close();
1080
+ this.sessions.clear();
1081
+ }
1082
+ };
1083
+ /**
1084
+ * HTTP transport handler using Streamable HTTP (protocol version 2025-03-26)
1085
+ * Provides stateful session management with resumability support
1086
+ */
1087
+ var HttpTransportHandler = class {
1088
+ serverFactory;
1089
+ app;
1090
+ server = null;
1091
+ sessionManager;
1092
+ config;
1093
+ constructor(serverFactory, config) {
1094
+ this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
1095
+ this.app = express();
1096
+ this.sessionManager = new HttpFullSessionManager();
1097
+ this.config = {
1098
+ mode: config.mode,
1099
+ port: config.port ?? 3e3,
1100
+ host: config.host ?? "localhost"
1101
+ };
1102
+ this.setupMiddleware();
1103
+ this.setupRoutes();
1104
+ }
1105
+ setupMiddleware() {
1106
+ this.app.use(express.json());
1107
+ }
1108
+ setupRoutes() {
1109
+ this.app.post("/mcp", async (req, res) => {
1110
+ await this.handlePostRequest(req, res);
1111
+ });
1112
+ this.app.get("/mcp", async (req, res) => {
1113
+ await this.handleGetRequest(req, res);
1114
+ });
1115
+ this.app.delete("/mcp", async (req, res) => {
1116
+ await this.handleDeleteRequest(req, res);
1117
+ });
1118
+ this.app.get("/health", (_req, res) => {
1119
+ res.json({
1120
+ status: "ok",
1121
+ transport: "http"
1122
+ });
1123
+ });
1124
+ }
1125
+ async handlePostRequest(req, res) {
1126
+ const sessionId = req.headers["mcp-session-id"];
1127
+ let transport;
1128
+ if (sessionId && this.sessionManager.hasSession(sessionId)) transport = this.sessionManager.getSession(sessionId).transport;
1129
+ else if (!sessionId && isInitializeRequest(req.body)) {
1130
+ const mcpServer = this.serverFactory();
1131
+ transport = new StreamableHTTPServerTransport({
1132
+ sessionIdGenerator: () => randomUUID(),
1133
+ enableJsonResponse: true,
1134
+ onsessioninitialized: (sessionId$1) => {
1135
+ this.sessionManager.setSession(sessionId$1, transport, mcpServer);
1136
+ }
1137
+ });
1138
+ transport.onclose = () => {
1139
+ if (transport.sessionId) this.sessionManager.deleteSession(transport.sessionId);
1140
+ };
1141
+ await mcpServer.connect(transport);
1142
+ } else {
1143
+ res.status(400).json({
1144
+ jsonrpc: "2.0",
1145
+ error: {
1146
+ code: -32e3,
1147
+ message: "Bad Request: No valid session ID provided"
1148
+ },
1149
+ id: null
1150
+ });
1151
+ return;
1152
+ }
1153
+ await transport.handleRequest(req, res, req.body);
1154
+ }
1155
+ async handleGetRequest(req, res) {
1156
+ const sessionId = req.headers["mcp-session-id"];
1157
+ if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
1158
+ res.status(400).send("Invalid or missing session ID");
1159
+ return;
1160
+ }
1161
+ await this.sessionManager.getSession(sessionId).transport.handleRequest(req, res);
1162
+ }
1163
+ async handleDeleteRequest(req, res) {
1164
+ const sessionId = req.headers["mcp-session-id"];
1165
+ if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
1166
+ res.status(400).send("Invalid or missing session ID");
1167
+ return;
1168
+ }
1169
+ await this.sessionManager.getSession(sessionId).transport.handleRequest(req, res);
1170
+ this.sessionManager.deleteSession(sessionId);
1171
+ }
1172
+ async start() {
1173
+ return new Promise((resolve, reject) => {
1174
+ try {
1175
+ this.server = this.app.listen(this.config.port, this.config.host, () => {
1176
+ console.error(`@agiflowai/one-mcp MCP server started on http://${this.config.host}:${this.config.port}/mcp`);
1177
+ console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
1178
+ resolve();
1179
+ });
1180
+ this.server.on("error", (error) => {
1181
+ reject(error);
1182
+ });
1183
+ } catch (error) {
1184
+ reject(error);
1185
+ }
1186
+ });
1187
+ }
1188
+ async stop() {
1189
+ return new Promise((resolve, reject) => {
1190
+ if (this.server) {
1191
+ this.sessionManager.clear();
1192
+ this.server.close((err) => {
1193
+ if (err) reject(err);
1194
+ else {
1195
+ this.server = null;
1196
+ resolve();
1197
+ }
1198
+ });
1199
+ } else resolve();
1200
+ });
1201
+ }
1202
+ getPort() {
1203
+ return this.config.port;
1204
+ }
1205
+ getHost() {
1206
+ return this.config.host;
1207
+ }
1208
+ };
1209
+
1210
+ //#endregion
1211
+ export { McpClientManagerService as a, createServer as i, SseTransportHandler as n, ConfigFetcherService as o, StdioTransportHandler as r, HttpTransportHandler as t };