@myatkyawthu/mcp-connect 0.1.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.
@@ -0,0 +1,236 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
4
+ import { logger } from '../utils/logger.js';
5
+ import { sanitizeErrorMessage, validateToolArguments } from '../utils/validation.js';
6
+
7
+ // Default timeout for tool execution (30 seconds)
8
+ const DEFAULT_TOOL_TIMEOUT = 30000;
9
+
10
+ /**
11
+ * MCP Connect Server class
12
+ * Handles MCP protocol communication and tool execution
13
+ */
14
+ export class MCPConnectServer {
15
+ /**
16
+ * Create MCP Connect Server
17
+ * @param {import('../types/mcp.js').MCPConfig} config - MCP configuration
18
+ */
19
+ constructor(config) {
20
+ /** @type {import('../types/mcp.js').MCPConfig} */
21
+ this.config = config;
22
+ /** @type {boolean} */
23
+ this.isShuttingDown = false;
24
+
25
+ // Create MCP SDK server for STDIO transport
26
+ /** @type {Server} */
27
+ this.server = new Server(
28
+ {
29
+ name: config.name,
30
+ version: config.version,
31
+ description: config.description,
32
+ },
33
+ {
34
+ capabilities: {
35
+ tools: {},
36
+ },
37
+ },
38
+ );
39
+
40
+ this.setupHandlers();
41
+ this.setupGracefulShutdown();
42
+ }
43
+
44
+ /**
45
+ * Set up graceful shutdown handlers
46
+ */
47
+ setupGracefulShutdown() {
48
+ /**
49
+ * Shutdown handler
50
+ * @param {string} signal - Shutdown signal
51
+ */
52
+ const shutdown = async (signal) => {
53
+ if (this.isShuttingDown) return;
54
+ this.isShuttingDown = true;
55
+
56
+ logger.serverShutdown(signal);
57
+ try {
58
+ // Give ongoing operations a chance to complete
59
+ await new Promise(resolve => setTimeout(resolve, 1000));
60
+ process.exit(0);
61
+ } catch (error) {
62
+ logger.error("Error during shutdown", error);
63
+ process.exit(1);
64
+ }
65
+ };
66
+
67
+ process.on("SIGINT", () => shutdown("SIGINT"));
68
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
69
+ process.on("uncaughtException", (error) => {
70
+ logger.error("Uncaught exception", error);
71
+ shutdown("uncaughtException");
72
+ });
73
+ process.on("unhandledRejection", (reason) => {
74
+ logger.error("Unhandled rejection", reason);
75
+ shutdown("unhandledRejection");
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Execute promise with timeout
81
+ * @template T
82
+ * @param {Promise<T>} promise - Promise to execute
83
+ * @param {number} timeoutMs - Timeout in milliseconds
84
+ * @param {string} operation - Operation name for error messages
85
+ * @returns {Promise<T>} Promise that resolves or rejects with timeout
86
+ */
87
+ async withTimeout(promise, timeoutMs, operation) {
88
+ const timeoutPromise = new Promise((_, reject) => {
89
+ setTimeout(() => {
90
+ reject(new Error(`${operation} timed out after ${timeoutMs}ms`));
91
+ }, timeoutMs);
92
+ });
93
+
94
+ return Promise.race([promise, timeoutPromise]);
95
+ }
96
+
97
+ /**
98
+ * Set up MCP request handlers
99
+ */
100
+ setupHandlers() {
101
+ // List available tools
102
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
103
+ return {
104
+ tools: this.config.tools.map((tool) => ({
105
+ name: tool.name,
106
+ description: tool.description,
107
+ inputSchema: tool.inputSchema,
108
+ })),
109
+ };
110
+ });
111
+
112
+ // Execute tool calls with proper error handling and timeout
113
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
114
+ const requestId = Math.random().toString(36).substring(2, 8);
115
+
116
+ if (this.isShuttingDown) {
117
+ throw new Error("Server is shutting down");
118
+ }
119
+
120
+ const { name, arguments: args } = request.params;
121
+
122
+ // Log incoming request
123
+ logger.traceRequest("tools/call", { name, args }, requestId);
124
+
125
+ // Validate request
126
+ if (!name || typeof name !== "string") {
127
+ const error = new Error("Tool name is required and must be a string");
128
+ logger.traceError("tools/call", error, requestId);
129
+ throw error;
130
+ }
131
+
132
+ const tool = this.config.tools.find((t) => t.name === name);
133
+ if (!tool) {
134
+ const error = new Error(`Tool "${name}" not found. Available tools: ${this.config.tools.map(t => t.name).join(", ")}`);
135
+ logger.traceError("tools/call", error, requestId);
136
+ throw error;
137
+ }
138
+
139
+ const startTime = Date.now();
140
+ logger.toolExecutionStart(name, args, requestId);
141
+
142
+ try {
143
+ // Validate and sanitize arguments
144
+ const validatedArgs = validateToolArguments(args);
145
+
146
+ // Execute tool with timeout and performance tracking
147
+ const toolPromise = Promise.resolve(tool.handler(validatedArgs));
148
+ const result = await logger.timeAsync(
149
+ `Tool "${name}" execution`,
150
+ this.withTimeout(toolPromise, DEFAULT_TOOL_TIMEOUT, `Tool "${name}" execution`),
151
+ name
152
+ );
153
+
154
+ // Handle different result types
155
+ let content;
156
+ if (typeof result === "string") {
157
+ content = result;
158
+ } else if (result === null || result === undefined) {
159
+ content = "null";
160
+ } else {
161
+ try {
162
+ content = JSON.stringify(result, null, 2);
163
+ } catch (jsonError) {
164
+ content = String(result);
165
+ }
166
+ }
167
+
168
+ const duration = Date.now() - startTime;
169
+ logger.toolExecutionEnd(name, duration, requestId);
170
+ logger.traceResponse("tools/call", { content }, requestId);
171
+
172
+ return {
173
+ content: [
174
+ {
175
+ type: "text",
176
+ text: content,
177
+ },
178
+ ],
179
+ };
180
+ } catch (error) {
181
+ const duration = Date.now() - startTime;
182
+ logger.toolExecutionError(name, error, duration, requestId);
183
+ logger.traceError("tools/call", error, requestId);
184
+
185
+ // Return MCP-compliant error with sanitized message
186
+ const sanitizedMessage = sanitizeErrorMessage(error);
187
+ throw new Error(`Tool "${name}" failed: ${sanitizedMessage}`);
188
+ }
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Start the MCP server with STDIO transport
194
+ * @returns {Promise<void>} Promise that resolves when server starts
195
+ * @throws {Error} If server fails to start
196
+ */
197
+ async start() {
198
+ try {
199
+ const transport = new StdioServerTransport();
200
+
201
+ // Add error handling for transport
202
+ transport.onclose = () => {
203
+ if (!this.isShuttingDown) {
204
+ logger.warn("STDIO transport closed unexpectedly");
205
+ }
206
+ };
207
+
208
+ transport.onerror = (error) => {
209
+ logger.error("STDIO transport error", error);
210
+ };
211
+
212
+ await this.server.connect(transport);
213
+ logger.serverStarted(this.config.name, "STDIO", this.config.tools.length);
214
+ } catch (error) {
215
+ logger.error("Failed to start MCP server", error);
216
+
217
+ if (error instanceof Error && error.message.includes("EACCES")) {
218
+ logger.error("Permission denied. Check file/directory permissions.");
219
+ } else if (error instanceof Error && error.message.includes("ENOENT")) {
220
+ logger.error("File or directory not found. Check your configuration.");
221
+ }
222
+
223
+ throw error;
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Stop the MCP server
229
+ * @returns {Promise<void>} Promise that resolves when server stops
230
+ */
231
+ async stop() {
232
+ this.isShuttingDown = true;
233
+ // STDIO transport doesn't need explicit stopping
234
+ logger.info("MCP server stopped");
235
+ }
236
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * MCP-compliant types using official SDK
3
+ * This file contains JSDoc type definitions for MCP-Connect
4
+ */
5
+
6
+ /**
7
+ * @typedef {Object} MCPTool
8
+ * @property {string} name - Tool name
9
+ * @property {string} description - Tool description
10
+ * @property {Record<string, any>} inputSchema - JSON Schema for input validation
11
+ * @property {function(Record<string, any>): Promise<any>|any} handler - Tool execution function
12
+ */
13
+
14
+ /**
15
+ * @typedef {Object} MCPConfig
16
+ * @property {string} name - Server name
17
+ * @property {string} version - Server version
18
+ * @property {string} [description] - Server description
19
+ * @property {MCPTool[]} tools - Array of MCP tools
20
+ */
21
+
22
+ /**
23
+ * @typedef {Object} MCPServerOptions
24
+ * @property {MCPConfig} config - MCP configuration
25
+ */
26
+
27
+ /**
28
+ * Legacy types for backward compatibility
29
+ */
30
+
31
+ /**
32
+ * @typedef {function(...any[]): Promise<any>|any} ToolFunction
33
+ */
34
+
35
+ /**
36
+ * Tool definition - supports both tuple and object formats
37
+ * @typedef {[string, ToolFunction]|{name: string, handler: ToolFunction, description?: string, schema?: Record<string, any>}} ToolDefinition
38
+ *
39
+ * Tuple format: [name, handler]
40
+ * @example ['hello', async (args) => `Hello ${args.name}!`]
41
+ *
42
+ * Object format: {name, handler, description?, schema?}
43
+ * @example {
44
+ * name: 'hello',
45
+ * handler: async (args) => `Hello ${args.name}!`,
46
+ * description: 'Greet a user',
47
+ * schema: { type: 'object', properties: { name: { type: 'string' } } }
48
+ * }
49
+ */
50
+
51
+ // Runtime validation functions for type checking
52
+
53
+ /**
54
+ * Validate MCPTool object
55
+ * @param {any} tool - Tool to validate
56
+ * @returns {boolean} Whether the tool is valid
57
+ */
58
+ export function isValidMCPTool(tool) {
59
+ return (
60
+ tool &&
61
+ typeof tool === 'object' &&
62
+ typeof tool.name === 'string' &&
63
+ typeof tool.description === 'string' &&
64
+ typeof tool.inputSchema === 'object' &&
65
+ typeof tool.handler === 'function'
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Validate MCPConfig object
71
+ * @param {any} config - Config to validate
72
+ * @returns {boolean} Whether the config is valid
73
+ */
74
+ export function isValidMCPConfig(config) {
75
+ return (
76
+ config &&
77
+ typeof config === 'object' &&
78
+ typeof config.name === 'string' &&
79
+ typeof config.version === 'string' &&
80
+ Array.isArray(config.tools) &&
81
+ config.tools.every(isValidMCPTool)
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Validate ToolDefinition (tuple or object format)
87
+ * @param {any} toolDef - Tool definition to validate
88
+ * @returns {boolean} Whether the tool definition is valid
89
+ */
90
+ export function isValidToolDefinition(toolDef) {
91
+ if (Array.isArray(toolDef)) {
92
+ // Tuple format: [name, handler]
93
+ return (
94
+ toolDef.length === 2 &&
95
+ typeof toolDef[0] === 'string' &&
96
+ typeof toolDef[1] === 'function'
97
+ );
98
+ } else if (toolDef && typeof toolDef === 'object') {
99
+ // Object format: {name, handler, description?, schema?}
100
+ return (
101
+ typeof toolDef.name === 'string' &&
102
+ typeof toolDef.handler === 'function' &&
103
+ (toolDef.description === undefined || typeof toolDef.description === 'string') &&
104
+ (toolDef.schema === undefined || (typeof toolDef.schema === 'object' && toolDef.schema !== null))
105
+ );
106
+ }
107
+ return false;
108
+ }