mcp-ts-template 1.1.6
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/LICENSE +201 -0
- package/README.md +233 -0
- package/dist/config/index.d.ts +73 -0
- package/dist/config/index.js +125 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +162 -0
- package/dist/mcp-client/client.d.ts +36 -0
- package/dist/mcp-client/client.js +276 -0
- package/dist/mcp-client/configLoader.d.ts +75 -0
- package/dist/mcp-client/configLoader.js +203 -0
- package/dist/mcp-client/index.d.ts +10 -0
- package/dist/mcp-client/index.js +14 -0
- package/dist/mcp-client/transport.d.ts +34 -0
- package/dist/mcp-client/transport.js +183 -0
- package/dist/mcp-server/resources/echoResource/echoResourceLogic.d.ts +38 -0
- package/dist/mcp-server/resources/echoResource/echoResourceLogic.js +40 -0
- package/dist/mcp-server/resources/echoResource/index.d.ts +5 -0
- package/dist/mcp-server/resources/echoResource/index.js +5 -0
- package/dist/mcp-server/resources/echoResource/registration.d.ts +12 -0
- package/dist/mcp-server/resources/echoResource/registration.js +122 -0
- package/dist/mcp-server/server.d.ts +27 -0
- package/dist/mcp-server/server.js +176 -0
- package/dist/mcp-server/tools/echoTool/echoToolLogic.d.ts +68 -0
- package/dist/mcp-server/tools/echoTool/echoToolLogic.js +73 -0
- package/dist/mcp-server/tools/echoTool/index.d.ts +5 -0
- package/dist/mcp-server/tools/echoTool/index.js +5 -0
- package/dist/mcp-server/tools/echoTool/registration.d.ts +12 -0
- package/dist/mcp-server/tools/echoTool/registration.js +86 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.d.ts +57 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.js +145 -0
- package/dist/mcp-server/transports/httpTransport.d.ts +23 -0
- package/dist/mcp-server/transports/httpTransport.js +411 -0
- package/dist/mcp-server/transports/stdioTransport.d.ts +40 -0
- package/dist/mcp-server/transports/stdioTransport.js +70 -0
- package/dist/types-global/errors.d.ts +73 -0
- package/dist/types-global/errors.js +66 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +12 -0
- package/dist/utils/internal/errorHandler.d.ts +90 -0
- package/dist/utils/internal/errorHandler.js +247 -0
- package/dist/utils/internal/index.d.ts +3 -0
- package/dist/utils/internal/index.js +3 -0
- package/dist/utils/internal/logger.d.ts +50 -0
- package/dist/utils/internal/logger.js +267 -0
- package/dist/utils/internal/requestContext.d.ts +47 -0
- package/dist/utils/internal/requestContext.js +48 -0
- package/dist/utils/metrics/index.d.ts +1 -0
- package/dist/utils/metrics/index.js +1 -0
- package/dist/utils/metrics/tokenCounter.d.ts +27 -0
- package/dist/utils/metrics/tokenCounter.js +124 -0
- package/dist/utils/parsing/dateParser.d.ts +27 -0
- package/dist/utils/parsing/dateParser.js +62 -0
- package/dist/utils/parsing/index.d.ts +2 -0
- package/dist/utils/parsing/index.js +2 -0
- package/dist/utils/parsing/jsonParser.d.ts +46 -0
- package/dist/utils/parsing/jsonParser.js +79 -0
- package/dist/utils/security/idGenerator.d.ts +93 -0
- package/dist/utils/security/idGenerator.js +147 -0
- package/dist/utils/security/index.d.ts +3 -0
- package/dist/utils/security/index.js +3 -0
- package/dist/utils/security/rateLimiter.d.ts +92 -0
- package/dist/utils/security/rateLimiter.js +171 -0
- package/dist/utils/security/sanitization.d.ts +180 -0
- package/dist/utils/security/sanitization.js +372 -0
- package/package.json +79 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
// Import utils from the main barrel file (logger, RequestContext, requestContextService from ../utils/internal/*)
|
|
6
|
+
import { logger, requestContextService, } from "../utils/index.js";
|
|
7
|
+
// Import local McpError and BaseErrorCode
|
|
8
|
+
import { BaseErrorCode, McpError } from "../types-global/errors.js";
|
|
9
|
+
// --- Zod Schemas for Configuration Validation ---
|
|
10
|
+
// Ensures the configuration structure adheres to expected formats.
|
|
11
|
+
// Schema for environment variables passed to the server process (optional).
|
|
12
|
+
// Allows defining specific environment variables for each server.
|
|
13
|
+
const EnvSchema = z
|
|
14
|
+
.record(z.string())
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Optional key-value pairs for environment variables specific to the server process.");
|
|
17
|
+
// Schema for a single MCP server configuration entry within the main config file.
|
|
18
|
+
const McpServerConfigEntrySchema = z.object({
|
|
19
|
+
// The command or script used to launch the MCP server process (e.g., 'node', '/path/to/server.py').
|
|
20
|
+
// For HTTP transport, this field typically holds the base URL of the server.
|
|
21
|
+
command: z
|
|
22
|
+
.string()
|
|
23
|
+
.min(1, "Server command or HTTP base URL cannot be empty"),
|
|
24
|
+
// Arguments to pass to the server command (only applicable for stdio transport).
|
|
25
|
+
args: z.array(z.string()).default([]),
|
|
26
|
+
// Optional environment variables specific to this server process (merged with client's env).
|
|
27
|
+
env: EnvSchema,
|
|
28
|
+
// Specifies the communication method (stdio or http). Defaults to 'stdio'.
|
|
29
|
+
transportType: z.enum(["stdio", "http"]).default("stdio"),
|
|
30
|
+
// Optional fields for future use:
|
|
31
|
+
// disabled: z.boolean().optional().describe("If true, this server configuration is ignored."),
|
|
32
|
+
// autoApprove: z.boolean().optional().describe("If true, skip user approval prompts for this server (use with caution)."),
|
|
33
|
+
});
|
|
34
|
+
// Schema for the root structure of the mcp-config.json file.
|
|
35
|
+
// Expects a top-level key 'mcpServers' containing a map of server names to their configurations.
|
|
36
|
+
const McpClientConfigFileSchema = z.object({
|
|
37
|
+
mcpServers: z.record(McpServerConfigEntrySchema),
|
|
38
|
+
});
|
|
39
|
+
// --- Configuration Loading Logic ---
|
|
40
|
+
// Determine the directory of the current module to locate config files reliably.
|
|
41
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
// Define paths for the primary configuration file and the example fallback.
|
|
43
|
+
const primaryConfigPath = join(__dirname, "mcp-config.json");
|
|
44
|
+
const exampleConfigPath = join(__dirname, "mcp-config.json.example");
|
|
45
|
+
// Cache for the loaded configuration to avoid redundant file I/O and parsing.
|
|
46
|
+
let loadedConfig = null;
|
|
47
|
+
// Track which configuration file was successfully loaded (primary or example).
|
|
48
|
+
let loadedConfigPath = null;
|
|
49
|
+
/**
|
|
50
|
+
* Loads, validates, and caches the MCP client configuration.
|
|
51
|
+
* It first attempts to load from `mcp-config.json`. If that file doesn't exist
|
|
52
|
+
* or fails to read, it falls back to loading `mcp-config.json.example`.
|
|
53
|
+
* The loaded content is then parsed as JSON and validated against the Zod schema.
|
|
54
|
+
*
|
|
55
|
+
* @param parentContext - Optional parent request context for logging and tracing.
|
|
56
|
+
* @returns The loaded and validated MCP server configurations object.
|
|
57
|
+
* @throws McpError if neither config file can be read, parsed, or validated successfully.
|
|
58
|
+
*/
|
|
59
|
+
export function loadMcpClientConfig(parentContext) {
|
|
60
|
+
const context = requestContextService.createRequestContext({
|
|
61
|
+
...(parentContext ?? {}),
|
|
62
|
+
operation: "loadMcpClientConfig",
|
|
63
|
+
});
|
|
64
|
+
// --- Return Cached Config (if available) ---
|
|
65
|
+
if (loadedConfig && loadedConfigPath) {
|
|
66
|
+
logger.debug(`Returning cached MCP client config from: ${loadedConfigPath}`, context);
|
|
67
|
+
return loadedConfig;
|
|
68
|
+
}
|
|
69
|
+
let fileContent = null;
|
|
70
|
+
let configPathToLog = ""; // Store the path of the file being processed for logging
|
|
71
|
+
// --- 1. Attempt to Load Primary Config File ---
|
|
72
|
+
if (existsSync(primaryConfigPath)) {
|
|
73
|
+
logger.info(`Attempting to load primary MCP config: ${primaryConfigPath}`, context);
|
|
74
|
+
try {
|
|
75
|
+
fileContent = readFileSync(primaryConfigPath, "utf-8");
|
|
76
|
+
configPathToLog = primaryConfigPath;
|
|
77
|
+
logger.info(`Successfully read primary config file.`, {
|
|
78
|
+
...context,
|
|
79
|
+
filePath: configPathToLog,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (readError) {
|
|
83
|
+
logger.warning(`Failed to read primary config file at ${primaryConfigPath}, attempting fallback.`, {
|
|
84
|
+
...context,
|
|
85
|
+
filePath: primaryConfigPath,
|
|
86
|
+
error: readError instanceof Error ? readError.message : String(readError),
|
|
87
|
+
});
|
|
88
|
+
// Error reading primary file, proceed to fallback.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
logger.info(`Primary config file not found at ${primaryConfigPath}, attempting fallback.`, {
|
|
93
|
+
...context,
|
|
94
|
+
filePath: primaryConfigPath,
|
|
95
|
+
});
|
|
96
|
+
// Primary file doesn't exist, proceed to fallback.
|
|
97
|
+
}
|
|
98
|
+
// --- 2. Attempt to Load Example Config File (if primary failed) ---
|
|
99
|
+
if (!fileContent) {
|
|
100
|
+
if (existsSync(exampleConfigPath)) {
|
|
101
|
+
logger.info(`Attempting to load example MCP config: ${exampleConfigPath}`, context);
|
|
102
|
+
try {
|
|
103
|
+
fileContent = readFileSync(exampleConfigPath, "utf-8");
|
|
104
|
+
configPathToLog = exampleConfigPath;
|
|
105
|
+
logger.info(`Successfully read example config file.`, {
|
|
106
|
+
...context,
|
|
107
|
+
filePath: configPathToLog,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (readError) {
|
|
111
|
+
// If reading the example file also fails, it's a critical error.
|
|
112
|
+
logger.error(`Failed to read example config file as well.`, {
|
|
113
|
+
...context,
|
|
114
|
+
filePath: exampleConfigPath,
|
|
115
|
+
error: readError instanceof Error ? readError.message : String(readError),
|
|
116
|
+
});
|
|
117
|
+
throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, `Failed to read MCP client config: Neither ${primaryConfigPath} nor ${exampleConfigPath} could be read.`, { originalError: readError });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// If neither file exists, it's a critical error.
|
|
122
|
+
logger.error(`Neither primary nor example config file found.`, {
|
|
123
|
+
...context,
|
|
124
|
+
primaryPath: primaryConfigPath,
|
|
125
|
+
examplePath: exampleConfigPath,
|
|
126
|
+
});
|
|
127
|
+
throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, `MCP client config file not found: Looked for ${primaryConfigPath} and ${exampleConfigPath}.`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// --- 3. Parse and Validate JSON Content ---
|
|
131
|
+
try {
|
|
132
|
+
// Parse the raw file content into a JavaScript object.
|
|
133
|
+
const parsedJson = JSON.parse(fileContent);
|
|
134
|
+
// Validate the parsed object against the defined Zod schema.
|
|
135
|
+
const validationResult = McpClientConfigFileSchema.safeParse(parsedJson);
|
|
136
|
+
if (!validationResult.success) {
|
|
137
|
+
// Validation failed; log the specific Zod errors.
|
|
138
|
+
logger.error("MCP client configuration validation failed.", {
|
|
139
|
+
...context,
|
|
140
|
+
filePath: configPathToLog,
|
|
141
|
+
errors: validationResult.error.errors, // Detailed Zod error info
|
|
142
|
+
});
|
|
143
|
+
// Format Zod errors into a user-friendly message.
|
|
144
|
+
const errorMessages = validationResult.error.errors
|
|
145
|
+
.map((e) => `${e.path.join(".")}: ${e.message}`)
|
|
146
|
+
.join("; ");
|
|
147
|
+
throw new Error(`Validation failed: ${errorMessages}`); // Throw standard Error to be caught below
|
|
148
|
+
}
|
|
149
|
+
// --- Validation Successful ---
|
|
150
|
+
// Cache the validated configuration and the path it was loaded from.
|
|
151
|
+
loadedConfig = validationResult.data;
|
|
152
|
+
loadedConfigPath = configPathToLog;
|
|
153
|
+
logger.info(`MCP client configuration loaded and validated successfully from: ${loadedConfigPath}`, {
|
|
154
|
+
...context,
|
|
155
|
+
serversFound: Object.keys(loadedConfig.mcpServers).length,
|
|
156
|
+
});
|
|
157
|
+
// Return the validated configuration.
|
|
158
|
+
return loadedConfig;
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
// Catch errors from JSON.parse or the Zod validation failure.
|
|
162
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
163
|
+
logger.error("Failed to parse or validate MCP client configuration", {
|
|
164
|
+
...context,
|
|
165
|
+
filePath: configPathToLog,
|
|
166
|
+
error: errorMessage,
|
|
167
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
168
|
+
});
|
|
169
|
+
// Throw a specific McpError indicating a configuration problem.
|
|
170
|
+
throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, `Failed to load/validate MCP client config from ${configPathToLog}: ${errorMessage}`, { originalError: error });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Retrieves the configuration entry for a specific MCP server by its name.
|
|
175
|
+
* Ensures the main configuration is loaded (using the cached version if available)
|
|
176
|
+
* before attempting to access the specific server's details.
|
|
177
|
+
*
|
|
178
|
+
* @param serverName - The name identifier of the server (key in the 'mcpServers' map).
|
|
179
|
+
* @param parentContext - Optional parent request context for logging.
|
|
180
|
+
* @returns A copy of the configuration entry for the specified server.
|
|
181
|
+
* @throws McpError if the configuration hasn't been loaded or the server name is not found within the loaded config.
|
|
182
|
+
*/
|
|
183
|
+
export function getMcpServerConfig(serverName, parentContext) {
|
|
184
|
+
const context = requestContextService.createRequestContext({
|
|
185
|
+
...(parentContext ?? {}),
|
|
186
|
+
operation: "getMcpServerConfig",
|
|
187
|
+
targetServer: serverName,
|
|
188
|
+
});
|
|
189
|
+
// Ensure the main config is loaded (uses cache or loads/validates).
|
|
190
|
+
const config = loadMcpClientConfig(context);
|
|
191
|
+
// Get the path from where the config was loaded for logging clarity.
|
|
192
|
+
const configPath = loadedConfigPath || "unknown";
|
|
193
|
+
// Attempt to retrieve the specific server's configuration.
|
|
194
|
+
const serverConfig = config.mcpServers[serverName];
|
|
195
|
+
if (!serverConfig) {
|
|
196
|
+
// If the server name doesn't exist as a key in the config.
|
|
197
|
+
logger.error(`Configuration for MCP server "${serverName}" not found in ${configPath}.`, context);
|
|
198
|
+
throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, `Configuration for MCP server "${serverName}" not found in ${configPath}.`);
|
|
199
|
+
}
|
|
200
|
+
logger.debug(`Retrieved configuration for server "${serverName}" from ${configPath}`, context);
|
|
201
|
+
// Return a shallow copy to prevent accidental modification of the cached configuration object.
|
|
202
|
+
return { ...serverConfig };
|
|
203
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel file for the MCP Client module (`src/mcp-client`).
|
|
3
|
+
* This file re-exports the primary functions and types related to creating,
|
|
4
|
+
* configuring, connecting, and managing MCP client instances based on the
|
|
5
|
+
* MCP 2025-03-26 specification and the high-level TypeScript SDK.
|
|
6
|
+
*/
|
|
7
|
+
export { connectMcpClient, disconnectAllMcpClients, disconnectMcpClient, type ConnectedMcpClient, } from "./client.js";
|
|
8
|
+
export { getMcpServerConfig, loadMcpClientConfig, type McpServerConfigEntry, // Export the type for a single server's config
|
|
9
|
+
type McpClientConfigFile, } from "./configLoader.js";
|
|
10
|
+
export { createStdioClientTransport, getClientTransport } from "./transport.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel file for the MCP Client module (`src/mcp-client`).
|
|
3
|
+
* This file re-exports the primary functions and types related to creating,
|
|
4
|
+
* configuring, connecting, and managing MCP client instances based on the
|
|
5
|
+
* MCP 2025-03-26 specification and the high-level TypeScript SDK.
|
|
6
|
+
*/
|
|
7
|
+
// Export core client connection management functions and the connected client type alias.
|
|
8
|
+
export { connectMcpClient, disconnectAllMcpClients, disconnectMcpClient, } from "./client.js";
|
|
9
|
+
// Export configuration loading functions and related types.
|
|
10
|
+
// These handle reading and validating server connection details from `mcp-config.json`.
|
|
11
|
+
export { getMcpServerConfig, loadMcpClientConfig, } from "./configLoader.js";
|
|
12
|
+
// Export transport creation functions.
|
|
13
|
+
// `getClientTransport` acts as a factory based on the server's configured `transportType`.
|
|
14
|
+
export { createStdioClientTransport, getClientTransport } from "./transport.js";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
|
+
import { RequestContext } from "../utils/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options specifically required for creating a StdioClientTransport.
|
|
6
|
+
* This includes the command to execute, arguments, and optional environment variables.
|
|
7
|
+
*/
|
|
8
|
+
interface StdioTransportConfig {
|
|
9
|
+
command: string;
|
|
10
|
+
args: string[];
|
|
11
|
+
env?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Creates and configures a StdioClientTransport instance for launching and communicating
|
|
15
|
+
* with an MCP server process via standard input/output.
|
|
16
|
+
*
|
|
17
|
+
* @param transportConfig - Configuration containing the command, args, and env for the server process.
|
|
18
|
+
* @param parentContext - Optional parent request context for logging.
|
|
19
|
+
* @returns A configured StdioClientTransport instance ready to be connected.
|
|
20
|
+
* @throws McpError if the provided configuration is invalid or if the transport fails to initialize.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createStdioClientTransport(transportConfig: StdioTransportConfig, parentContext?: RequestContext | null): StdioClientTransport;
|
|
23
|
+
/**
|
|
24
|
+
* Retrieves the server configuration and creates the appropriate client transport
|
|
25
|
+
* (Stdio or HTTP) based on the `transportType` specified in the configuration.
|
|
26
|
+
* This acts as a factory function for obtaining the correct transport instance.
|
|
27
|
+
*
|
|
28
|
+
* @param serverName - The name of the MCP server (key in mcp-config.json).
|
|
29
|
+
* @param parentContext - Optional parent request context for logging.
|
|
30
|
+
* @returns A configured transport instance (either StdioClientTransport or StreamableHTTPClientTransport).
|
|
31
|
+
* @throws McpError if configuration is missing, invalid, specifies an unsupported transport type, or if transport creation fails.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getClientTransport(serverName: string, parentContext?: RequestContext | null): StdioClientTransport | StreamableHTTPClientTransport;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; // Corrected Import HTTP transport name
|
|
3
|
+
// Import utils from the main barrel file (logger, RequestContext, requestContextService from ../utils/internal/*)
|
|
4
|
+
import { BaseErrorCode, McpError } from "../types-global/errors.js";
|
|
5
|
+
import { logger, requestContextService, } from "../utils/index.js";
|
|
6
|
+
import { getMcpServerConfig } from "./configLoader.js";
|
|
7
|
+
/**
|
|
8
|
+
* Creates and configures a StdioClientTransport instance for launching and communicating
|
|
9
|
+
* with an MCP server process via standard input/output.
|
|
10
|
+
*
|
|
11
|
+
* @param transportConfig - Configuration containing the command, args, and env for the server process.
|
|
12
|
+
* @param parentContext - Optional parent request context for logging.
|
|
13
|
+
* @returns A configured StdioClientTransport instance ready to be connected.
|
|
14
|
+
* @throws McpError if the provided configuration is invalid or if the transport fails to initialize.
|
|
15
|
+
*/
|
|
16
|
+
export function createStdioClientTransport(transportConfig, parentContext) {
|
|
17
|
+
const baseContext = parentContext ? { ...parentContext } : {};
|
|
18
|
+
const context = requestContextService.createRequestContext({
|
|
19
|
+
...baseContext,
|
|
20
|
+
operation: "createStdioClientTransport",
|
|
21
|
+
transportType: "stdio",
|
|
22
|
+
command: transportConfig.command,
|
|
23
|
+
});
|
|
24
|
+
logger.debug("Creating StdioClientTransport", context);
|
|
25
|
+
// --- Input Validation ---
|
|
26
|
+
if (!transportConfig.command || typeof transportConfig.command !== "string") {
|
|
27
|
+
logger.error("Invalid command provided for StdioClientTransport", context);
|
|
28
|
+
throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, "Invalid command for StdioClientTransport", context);
|
|
29
|
+
}
|
|
30
|
+
if (!Array.isArray(transportConfig.args)) {
|
|
31
|
+
logger.error("Invalid args provided for StdioClientTransport (must be an array)", context);
|
|
32
|
+
throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, "Invalid args for StdioClientTransport (must be an array)", context);
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
// --- Environment Merging ---
|
|
36
|
+
// Combine the client's current environment with server-specific variables from the config.
|
|
37
|
+
// Server-specific variables take precedence.
|
|
38
|
+
// Filter out undefined values from process.env to avoid potential issues.
|
|
39
|
+
const filteredProcessEnv = {};
|
|
40
|
+
for (const key in process.env) {
|
|
41
|
+
if (Object.prototype.hasOwnProperty.call(process.env, key) &&
|
|
42
|
+
process.env[key] !== undefined) {
|
|
43
|
+
filteredProcessEnv[key] = process.env[key];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const mergedEnv = {
|
|
47
|
+
...filteredProcessEnv, // Start with client's environment
|
|
48
|
+
...(transportConfig.env || {}), // Override/add server-specific env vars
|
|
49
|
+
};
|
|
50
|
+
logger.debug("Creating StdioClientTransport with merged environment", {
|
|
51
|
+
...context,
|
|
52
|
+
envKeys: Object.keys(mergedEnv).length, // Log count for brevity
|
|
53
|
+
});
|
|
54
|
+
// --- Instantiate Transport ---
|
|
55
|
+
// Create the transport instance, passing the command, args, and merged environment.
|
|
56
|
+
const transport = new StdioClientTransport({
|
|
57
|
+
command: transportConfig.command,
|
|
58
|
+
args: transportConfig.args,
|
|
59
|
+
env: mergedEnv,
|
|
60
|
+
});
|
|
61
|
+
logger.info("StdioClientTransport created successfully", context);
|
|
62
|
+
return transport;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
// Catch errors during transport instantiation (e.g., invalid command path).
|
|
66
|
+
logger.error("Failed to create StdioClientTransport", {
|
|
67
|
+
...context,
|
|
68
|
+
error: error instanceof Error ? error.message : String(error),
|
|
69
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
70
|
+
});
|
|
71
|
+
// Re-throw as a specific McpError for consistent error handling.
|
|
72
|
+
throw new McpError(BaseErrorCode.INTERNAL_ERROR, // Could potentially be CONFIGURATION_ERROR if command is invalid
|
|
73
|
+
`Failed to create StdioClientTransport: ${error instanceof Error ? error.message : String(error)}`, { originalError: error, ...context });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Retrieves the server configuration and creates the appropriate client transport
|
|
78
|
+
* (Stdio or HTTP) based on the `transportType` specified in the configuration.
|
|
79
|
+
* This acts as a factory function for obtaining the correct transport instance.
|
|
80
|
+
*
|
|
81
|
+
* @param serverName - The name of the MCP server (key in mcp-config.json).
|
|
82
|
+
* @param parentContext - Optional parent request context for logging.
|
|
83
|
+
* @returns A configured transport instance (either StdioClientTransport or StreamableHTTPClientTransport).
|
|
84
|
+
* @throws McpError if configuration is missing, invalid, specifies an unsupported transport type, or if transport creation fails.
|
|
85
|
+
*/
|
|
86
|
+
export function getClientTransport(serverName, parentContext) {
|
|
87
|
+
const baseContext = parentContext ? { ...parentContext } : {};
|
|
88
|
+
const context = requestContextService.createRequestContext({
|
|
89
|
+
...baseContext,
|
|
90
|
+
operation: "getClientTransport",
|
|
91
|
+
targetServer: serverName,
|
|
92
|
+
});
|
|
93
|
+
logger.info(`Getting transport for server: ${serverName}`, context);
|
|
94
|
+
try {
|
|
95
|
+
// --- 1. Load Server Configuration ---
|
|
96
|
+
// Retrieve the validated configuration for the specified server.
|
|
97
|
+
const serverConfig = getMcpServerConfig(serverName, context);
|
|
98
|
+
// --- 2. Determine and Create Transport ---
|
|
99
|
+
const transportType = serverConfig.transportType; // Already defaulted to 'stdio' by config loader if missing
|
|
100
|
+
logger.info(`Selected transport type "${transportType}" for server: ${serverName}`, { ...context, transportType });
|
|
101
|
+
if (transportType === "stdio") {
|
|
102
|
+
// --- Create Stdio Transport ---
|
|
103
|
+
logger.info(`Creating stdio transport for server: ${serverName}`, {
|
|
104
|
+
...context,
|
|
105
|
+
command: serverConfig.command,
|
|
106
|
+
args: serverConfig.args,
|
|
107
|
+
envProvided: !!serverConfig.env,
|
|
108
|
+
});
|
|
109
|
+
// Delegate to the dedicated stdio creation function.
|
|
110
|
+
return createStdioClientTransport({
|
|
111
|
+
command: serverConfig.command,
|
|
112
|
+
args: serverConfig.args,
|
|
113
|
+
env: serverConfig.env, // Pass validated env config
|
|
114
|
+
}, context);
|
|
115
|
+
}
|
|
116
|
+
else if (transportType === "http") {
|
|
117
|
+
// --- Create HTTP Transport ---
|
|
118
|
+
// For HTTP, the 'command' field in the config is interpreted as the base URL.
|
|
119
|
+
const baseUrl = serverConfig.command;
|
|
120
|
+
// Validate that the command field looks like a URL for HTTP transport.
|
|
121
|
+
if (!baseUrl ||
|
|
122
|
+
typeof baseUrl !== "string" ||
|
|
123
|
+
!baseUrl.startsWith("http")) {
|
|
124
|
+
const httpConfigError = `Invalid configuration for HTTP transport server "${serverName}": The 'command' field must be a valid base URL (e.g., "http://localhost:3001"). Found: "${baseUrl}"`;
|
|
125
|
+
logger.error(httpConfigError, context);
|
|
126
|
+
throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, httpConfigError, context);
|
|
127
|
+
}
|
|
128
|
+
logger.info(`Creating HTTP transport for server: ${serverName} with base URL: ${baseUrl}`, context);
|
|
129
|
+
try {
|
|
130
|
+
// Instantiate the StreamableHTTPClientTransport using the base URL.
|
|
131
|
+
// The SDK handles constructing the full /mcp endpoint path.
|
|
132
|
+
// IMPORTANT: Authentication headers (e.g., Authorization: Bearer <token>)
|
|
133
|
+
// need to be added here or via client options if required by the server,
|
|
134
|
+
// following the MCP Authentication Specification.
|
|
135
|
+
const transportOptions = {
|
|
136
|
+
// Example: Adding authentication headers if needed
|
|
137
|
+
// headers: {
|
|
138
|
+
// 'Authorization': `Bearer ${getAuthTokenForServer(serverName)}`
|
|
139
|
+
// }
|
|
140
|
+
};
|
|
141
|
+
const transport = new StreamableHTTPClientTransport(new URL(baseUrl), transportOptions // Pass options if needed
|
|
142
|
+
);
|
|
143
|
+
logger.info(`StreamableHTTPClientTransport created successfully for ${serverName}`, context);
|
|
144
|
+
return transport;
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
// Catch errors during HTTP transport instantiation (e.g., invalid URL format).
|
|
148
|
+
logger.error(`Failed to create StreamableHttpClientTransport for ${serverName}`, {
|
|
149
|
+
...context,
|
|
150
|
+
baseUrl: baseUrl,
|
|
151
|
+
error: error instanceof Error ? error.message : String(error),
|
|
152
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
153
|
+
});
|
|
154
|
+
throw new McpError(BaseErrorCode.INTERNAL_ERROR, // Or CONFIGURATION_ERROR if URL is invalid
|
|
155
|
+
`Failed to create HTTP transport for ${serverName}: ${error instanceof Error ? error.message : String(error)}`, { originalError: error, ...context });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// --- Unsupported Transport Type ---
|
|
160
|
+
// This path should ideally not be reachable due to Zod validation in the config loader.
|
|
161
|
+
const unsupportedErrorMessage = `Unsupported transportType "${serverConfig.transportType}" configured for server "${serverName}".`;
|
|
162
|
+
logger.error(unsupportedErrorMessage, context);
|
|
163
|
+
throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, unsupportedErrorMessage, context);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
// Catch errors from config loading or transport creation steps.
|
|
168
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
169
|
+
logger.error(`Failed to get or create transport for server "${serverName}"`, {
|
|
170
|
+
...context,
|
|
171
|
+
error: errorMessage,
|
|
172
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
173
|
+
});
|
|
174
|
+
// Ensure a consistent McpError is thrown.
|
|
175
|
+
if (error instanceof McpError) {
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Assume configuration error if not already an McpError
|
|
180
|
+
throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, `Failed to initialize transport for ${serverName}: ${errorMessage}`, { originalError: error });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type RequestContext } from '../../../utils/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Zod schema defining the expected *query* parameters for the echo resource.
|
|
5
|
+
* Note: Path parameters (like '{message}' in 'echo://{message}') are defined
|
|
6
|
+
* in the ResourceTemplate and are typically extracted directly from the URI path,
|
|
7
|
+
* not validated by this schema. This schema focuses on optional or additional
|
|
8
|
+
* parameters passed via the query string (e.g., ?param=value).
|
|
9
|
+
* Used for validation and type inference of query parameters.
|
|
10
|
+
*/
|
|
11
|
+
export declare const querySchema: z.ZodObject<{
|
|
12
|
+
/** Optional message to be echoed back in the response. */
|
|
13
|
+
message: z.ZodOptional<z.ZodString>;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
message?: string | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
message?: string | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* TypeScript type inferred from the `querySchema`. Represents the validated query parameters.
|
|
21
|
+
* @typedef {z.infer<typeof querySchema>} EchoParams
|
|
22
|
+
*/
|
|
23
|
+
export type EchoParams = z.infer<typeof querySchema>;
|
|
24
|
+
/**
|
|
25
|
+
* Processes the core logic for the echo resource request.
|
|
26
|
+
* Takes the request URI and validated parameters, then constructs the response data.
|
|
27
|
+
*
|
|
28
|
+
* @function processEchoResource
|
|
29
|
+
* @param {URL} uri - The full URI of the incoming resource request.
|
|
30
|
+
* @param {EchoParams} params - The validated query parameters for the request.
|
|
31
|
+
* @param {RequestContext} context - The request context for logging and tracing.
|
|
32
|
+
* @returns {EchoResourceResponse} The data payload for the response (will be JSON-stringified by the handler).
|
|
33
|
+
*/
|
|
34
|
+
export declare const processEchoResource: (uri: URL, params: EchoParams, context: RequestContext) => {
|
|
35
|
+
message: string;
|
|
36
|
+
timestamp: string;
|
|
37
|
+
requestUri: string;
|
|
38
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// Import utils from the main barrel file (logger from ../../../utils/internal/logger.js, RequestContext from ../../../utils/internal/requestContext.js)
|
|
3
|
+
import { logger } from '../../../utils/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Zod schema defining the expected *query* parameters for the echo resource.
|
|
6
|
+
* Note: Path parameters (like '{message}' in 'echo://{message}') are defined
|
|
7
|
+
* in the ResourceTemplate and are typically extracted directly from the URI path,
|
|
8
|
+
* not validated by this schema. This schema focuses on optional or additional
|
|
9
|
+
* parameters passed via the query string (e.g., ?param=value).
|
|
10
|
+
* Used for validation and type inference of query parameters.
|
|
11
|
+
*/
|
|
12
|
+
export const querySchema = z.object({
|
|
13
|
+
/** Optional message to be echoed back in the response. */
|
|
14
|
+
message: z.string().optional()
|
|
15
|
+
.describe('Message to echo back in the response')
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Processes the core logic for the echo resource request.
|
|
19
|
+
* Takes the request URI and validated parameters, then constructs the response data.
|
|
20
|
+
*
|
|
21
|
+
* @function processEchoResource
|
|
22
|
+
* @param {URL} uri - The full URI of the incoming resource request.
|
|
23
|
+
* @param {EchoParams} params - The validated query parameters for the request.
|
|
24
|
+
* @param {RequestContext} context - The request context for logging and tracing.
|
|
25
|
+
* @returns {EchoResourceResponse} The data payload for the response (will be JSON-stringified by the handler).
|
|
26
|
+
*/
|
|
27
|
+
export const processEchoResource = (uri, params, context // Add context parameter
|
|
28
|
+
) => {
|
|
29
|
+
// The 'message' parameter is guaranteed by the ResourceTemplate match "echo://{message}"
|
|
30
|
+
// Use the value directly from the params object populated by the SDK.
|
|
31
|
+
const message = params.message || 'Default message if somehow empty'; // Added fallback just in case, though template should ensure it exists.
|
|
32
|
+
// Use the passed context for logging
|
|
33
|
+
logger.debug("Processing echo resource logic", { ...context, message });
|
|
34
|
+
// Prepare response data including timestamp and original URI
|
|
35
|
+
return {
|
|
36
|
+
message,
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
requestUri: uri.href
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
/**
|
|
3
|
+
* Registers the 'echo' resource and its handlers with the provided MCP server instance.
|
|
4
|
+
* This includes defining the resource template, metadata, query schema, examples,
|
|
5
|
+
* and the core request handling logic. Error handling is integrated using ErrorHandler.
|
|
6
|
+
*
|
|
7
|
+
* @function registerEchoResource
|
|
8
|
+
* @param {McpServer} server - The MCP server instance to register the resource with.
|
|
9
|
+
* @returns {Promise<void>} A promise that resolves when the resource registration is complete.
|
|
10
|
+
* @throws {McpError} Throws an McpError if the registration process fails critically.
|
|
11
|
+
*/
|
|
12
|
+
export declare const registerEchoResource: (server: McpServer) => Promise<void>;
|