@epicdm/flowstate-mcp-gateway 1.0.0 → 1.1.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.
- package/dist/cli.js +50 -25
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +50 -25
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +50 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +50 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -97,6 +97,36 @@ var MCPGatewayServer = class {
|
|
|
97
97
|
isInitialized = false;
|
|
98
98
|
httpServer;
|
|
99
99
|
transport;
|
|
100
|
+
/**
|
|
101
|
+
* Serialize {token-swap → sendRequest} sequences so concurrent dev
|
|
102
|
+
* requests can't race each other. Before Phase C this wrapper mutated
|
|
103
|
+
* `mcpServer.config.authToken` without any lock, so a slow request
|
|
104
|
+
* could pick up the token a faster concurrent request had just
|
|
105
|
+
* written — a classic confused-deputy inversion of privilege.
|
|
106
|
+
*
|
|
107
|
+
* The lock is chained: each caller awaits the previous tail, then
|
|
108
|
+
* claims the next tail. Errors don't poison the chain because we
|
|
109
|
+
* swallow them in the tail promise (callers still see their own
|
|
110
|
+
* error via the returned promise).
|
|
111
|
+
*/
|
|
112
|
+
requestChain = Promise.resolve();
|
|
113
|
+
async withTokenLock(token, op) {
|
|
114
|
+
const previous = this.requestChain;
|
|
115
|
+
let release;
|
|
116
|
+
this.requestChain = new Promise((resolve2) => {
|
|
117
|
+
release = resolve2;
|
|
118
|
+
});
|
|
119
|
+
try {
|
|
120
|
+
await previous.catch(() => void 0);
|
|
121
|
+
if (token !== void 0) {
|
|
122
|
+
;
|
|
123
|
+
this.mcpServer.config.authToken = token;
|
|
124
|
+
}
|
|
125
|
+
return await op();
|
|
126
|
+
} finally {
|
|
127
|
+
release();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
100
130
|
constructor(config2) {
|
|
101
131
|
this.config = config2;
|
|
102
132
|
this.app = (0, import_express.default)();
|
|
@@ -200,28 +230,19 @@ var MCPGatewayServer = class {
|
|
|
200
230
|
* This enables per-request auth so each API call can use its own token,
|
|
201
231
|
* rather than relying on the initial config token.
|
|
202
232
|
*/
|
|
203
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Extract a per-request bearer token from the Authorization header.
|
|
235
|
+
* Returns `undefined` on absent / malformed / unrecognised shapes so
|
|
236
|
+
* `withTokenLock` leaves the existing config token in place (rather
|
|
237
|
+
* than clobbering it with garbage).
|
|
238
|
+
*/
|
|
239
|
+
extractRequestToken(req) {
|
|
204
240
|
const authHeader = req.headers["authorization"];
|
|
205
|
-
if (!authHeader)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (!token) {
|
|
211
|
-
console.log("[MCP Gateway] Empty token after Bearer prefix removal");
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
if (!token.startsWith("eyJ") && !token.startsWith("epic_")) {
|
|
215
|
-
console.log(`[MCP Gateway] Invalid token format (starts with: ${token.substring(0, 10)}...)`);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
try {
|
|
219
|
-
;
|
|
220
|
-
this.mcpServer.config.authToken = token;
|
|
221
|
-
console.log("[MCP Gateway] Auth token updated from request");
|
|
222
|
-
} catch (error) {
|
|
223
|
-
console.error("[MCP Gateway] Failed to update auth config:", error);
|
|
224
|
-
}
|
|
241
|
+
if (!authHeader) return void 0;
|
|
242
|
+
const token = authHeader.toString().replace("Bearer ", "").trim();
|
|
243
|
+
if (!token) return void 0;
|
|
244
|
+
if (!token.startsWith("eyJ") && !token.startsWith("epic_")) return void 0;
|
|
245
|
+
return token;
|
|
225
246
|
}
|
|
226
247
|
setupRoutes() {
|
|
227
248
|
this.app.get("/health", (req, res) => {
|
|
@@ -238,8 +259,10 @@ var MCPGatewayServer = class {
|
|
|
238
259
|
error: "Server not initialized"
|
|
239
260
|
});
|
|
240
261
|
}
|
|
241
|
-
this.
|
|
242
|
-
|
|
262
|
+
const tools = await this.withTokenLock(
|
|
263
|
+
this.extractRequestToken(req),
|
|
264
|
+
() => this.listTools()
|
|
265
|
+
);
|
|
243
266
|
res.json({ tools });
|
|
244
267
|
} catch (error) {
|
|
245
268
|
console.error("Error listing tools:", error);
|
|
@@ -256,7 +279,6 @@ var MCPGatewayServer = class {
|
|
|
256
279
|
error: "Server not initialized"
|
|
257
280
|
});
|
|
258
281
|
}
|
|
259
|
-
this.updateAuthFromRequest(req);
|
|
260
282
|
const { toolName, arguments: args } = req.body;
|
|
261
283
|
if (!toolName) {
|
|
262
284
|
return res.status(400).json({
|
|
@@ -264,7 +286,10 @@ var MCPGatewayServer = class {
|
|
|
264
286
|
error: "toolName is required"
|
|
265
287
|
});
|
|
266
288
|
}
|
|
267
|
-
const result = await this.
|
|
289
|
+
const result = await this.withTokenLock(
|
|
290
|
+
this.extractRequestToken(req),
|
|
291
|
+
() => this.callTool(toolName, args || {})
|
|
292
|
+
);
|
|
268
293
|
const response = {
|
|
269
294
|
success: true,
|
|
270
295
|
result
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/InMemoryTransport.ts","../src/cli.ts"],"sourcesContent":["// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport express, { Express, Request, Response } from 'express'\nimport cors from 'cors'\nimport { FlowStateMCPServer } from '@epicdm/flowstate-mcp'\nimport { InMemoryTransport } from './InMemoryTransport'\nimport type { MCPGatewayConfig, ToolCallRequest, ToolCallResponse } from './types'\n\nexport class MCPGatewayServer {\n private app: Express\n private mcpServer: FlowStateMCPServer\n private config: MCPGatewayConfig\n private isInitialized = false\n private httpServer?: ReturnType<Express['listen']>\n private transport?: InMemoryTransport\n\n constructor(config: MCPGatewayConfig) {\n this.config = config\n this.app = express()\n\n // Initialize MCP Server\n this.mcpServer = new FlowStateMCPServer({\n rxdbServerUrl: config.rxdbServerUrl,\n domainId: config.domainId,\n projectPath: config.projectPath || process.cwd(),\n ...(config.userId && { userId: config.userId }),\n ...(config.orgId && { orgId: config.orgId }),\n } as any)\n\n this.setupMiddleware()\n this.setupRoutes()\n }\n\n /**\n * Get list of tools from MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for listing tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n */\n private async listTools(): Promise<any[]> {\n if (!this.transport) {\n return []\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/list' as const,\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to list tools')\n }\n\n return response.result?.tools || []\n }\n\n /**\n * Call a tool on the MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for calling tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n *\n * NOTE: The MCP SDK's server.request() method appears to require the request\n * object to be passed twice (once as the request, once as params). This seems\n * to be how the underlying MCP SDK expects the call to be structured based on\n * the protocol specification.\n */\n private async callTool(toolName: string, args: Record<string, any>): Promise<any> {\n if (!this.transport) {\n throw new Error('MCP server not available')\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/call' as const,\n params: {\n name: toolName,\n arguments: args,\n },\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to call tool')\n }\n\n return response.result?.content?.[0]?.text || response.result\n }\n\n private setupMiddleware(): void {\n // CORS\n this.app.use(\n cors({\n origin: this.config.corsOrigins || '*',\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n })\n )\n\n // Body parsing\n this.app.use(express.json())\n this.app.use(express.urlencoded({ extended: true }))\n\n // Request logging\n this.app.use((req, res, next) => {\n console.log(`${new Date().toISOString()} ${req.method} ${req.path}`)\n next()\n })\n }\n\n /**\n * Extract and forward auth token from request to MCP server\n *\n * This enables per-request auth so each API call can use its own token,\n * rather than relying on the initial config token.\n */\n private updateAuthFromRequest(req: Request): void {\n const authHeader = req.headers['authorization']\n if (!authHeader) {\n console.log('[MCP Gateway] No Authorization header in request')\n return\n }\n\n const token = authHeader.toString().replace('Bearer ', '')\n if (!token) {\n console.log('[MCP Gateway] Empty token after Bearer prefix removal')\n return\n }\n\n // Only accept valid-looking tokens (JWT or API tokens)\n if (!token.startsWith('eyJ') && !token.startsWith('epic_')) {\n console.log(`[MCP Gateway] Invalid token format (starts with: ${token.substring(0, 10)}...)`)\n return\n }\n\n try {\n ;(this.mcpServer as any).config.authToken = token\n console.log('[MCP Gateway] Auth token updated from request')\n } catch (error) {\n console.error('[MCP Gateway] Failed to update auth config:', error)\n }\n }\n\n private setupRoutes(): void {\n // Health check\n this.app.get('/health', (req: Request, res: Response) => {\n res.json({\n status: 'ok',\n initialized: this.isInitialized,\n timestamp: new Date().toISOString(),\n })\n })\n\n // List available tools\n this.app.get('/mcp/tools', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n error: 'Server not initialized',\n })\n }\n\n // Forward per-request auth to MCP server\n this.updateAuthFromRequest(req)\n\n const tools = await this.listTools()\n res.json({ tools })\n } catch (error) {\n console.error('Error listing tools:', error)\n res.status(500).json({\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })\n\n // Execute a tool\n this.app.post('/mcp/tools/call', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n success: false,\n error: 'Server not initialized',\n })\n }\n\n // Forward per-request auth to MCP server\n this.updateAuthFromRequest(req)\n\n const { toolName, arguments: args } = req.body as ToolCallRequest\n\n if (!toolName) {\n return res.status(400).json({\n success: false,\n error: 'toolName is required',\n })\n }\n\n const result = await this.callTool(toolName, args || {})\n\n const response: ToolCallResponse = {\n success: true,\n result,\n }\n\n res.json(response)\n } catch (error) {\n console.error('Error calling tool:', error)\n const response: ToolCallResponse = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n res.status(500).json(response)\n }\n })\n }\n\n async initialize(): Promise<void> {\n await this.mcpServer.initialize()\n\n // Connect the MCP server to an in-memory transport\n // This is required for the MCP SDK to handle requests\n this.transport = new InMemoryTransport()\n const server = (this.mcpServer as any).server\n if (server) {\n await server.connect(this.transport)\n }\n\n this.isInitialized = true\n console.log('MCP Gateway initialized')\n }\n\n async start(): Promise<void> {\n await this.initialize()\n\n return new Promise(resolve => {\n this.httpServer = this.app.listen(this.config.port, this.config.host, () => {\n console.log(`MCP Gateway listening on http://${this.config.host}:${this.config.port}`)\n resolve()\n })\n })\n }\n\n async close(): Promise<void> {\n // Close HTTP server first\n if (this.httpServer) {\n await new Promise<void>((resolve, reject) => {\n this.httpServer!.close(err => {\n if (err) {\n console.error('Error closing HTTP server:', err)\n reject(err)\n } else {\n console.log('HTTP server closed')\n resolve()\n }\n })\n })\n }\n\n // Then close MCP server\n await this.mcpServer.close()\n console.log('MCP Gateway closed')\n }\n}\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport type { JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, JSONRPCErrorResponse } from '@modelcontextprotocol/sdk/types.js';\nimport { EventEmitter } from 'events';\n\n/**\n * In-Memory Transport for programmatic MCP server usage\n *\n * This transport creates a bidirectional message channel that allows\n * the MCP server to be used programmatically without stdio/SSE.\n */\nexport class InMemoryTransport implements Transport {\n sessionId?: string;\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n private started = false;\n private emitter = new EventEmitter();\n private responseHandlers = new Map<string | number, (response: JSONRPCMessage) => void>();\n\n async start(): Promise<void> {\n this.started = true;\n this.sessionId = `inmemory-${Date.now()}-${Math.random().toString(36).substring(7)}`;\n }\n\n async send(message: JSONRPCMessage): Promise<void> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n // If this is a response (has 'result' or 'error' and 'id')\n if ('result' in message || 'error' in message) {\n const response = message as JSONRPCResponse | JSONRPCErrorResponse;\n const handler = this.responseHandlers.get(response.id!);\n if (handler) {\n handler(message);\n this.responseHandlers.delete(response.id!);\n }\n }\n }\n\n async close(): Promise<void> {\n this.started = false;\n this.responseHandlers.clear();\n this.onclose?.();\n }\n\n setProtocolVersion?(version: string): void {\n // No-op for in-memory transport\n }\n\n /**\n * Send a request and wait for response\n * Used by the gateway to make programmatic requests\n */\n async sendRequest(request: JSONRPCMessage): Promise<JSONRPCMessage> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n return new Promise((resolve, reject) => {\n const req = request as JSONRPCRequest;\n const timeout = setTimeout(() => {\n this.responseHandlers.delete(req.id);\n reject(new Error('Request timeout'));\n }, 60000);\n\n this.responseHandlers.set(req.id, (response) => {\n clearTimeout(timeout);\n resolve(response);\n });\n\n // Route request to server's message handler\n if (this.onmessage) {\n this.onmessage(request);\n } else {\n reject(new Error('Transport not connected to server'));\n }\n });\n }\n}\n","#!/usr/bin/env node\n// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport { MCPGatewayServer } from './server';\nimport { config } from 'dotenv';\nimport { resolve } from 'path';\n\n// Load environment variables\nconfig({ path: resolve(process.cwd(), '.env') });\n\n// Shutdown guard to prevent race conditions\nlet isShuttingDown = false;\n\n// Global error handlers\nprocess.on('unhandledRejection', (reason, promise) => {\n console.error('Unhandled Rejection at:', promise, 'reason:', reason);\n process.exit(1);\n});\n\nprocess.on('uncaughtException', (error) => {\n console.error('Uncaught Exception:', error);\n process.exit(1);\n});\n\nasync function main() {\n const gatewayConfig = {\n port: parseInt(process.env.MCP_GATEWAY_PORT || '7081', 10),\n host: process.env.MCP_GATEWAY_HOST || '0.0.0.0',\n rxdbServerUrl: process.env.RXDB_SERVER_URL || '',\n authToken: process.env.RXDB_AUTH_TOKEN || '',\n domainId: process.env.RXDB_DOMAIN_ID || '',\n projectPath: process.env.PROJECT_PATH,\n userId: process.env.USER_ID,\n orgId: process.env.ORG_ID,\n corsOrigins: process.env.CORS_ORIGINS?.split(','),\n };\n\n // Validate required config\n if (!gatewayConfig.rxdbServerUrl) {\n console.error('ERROR: RXDB_SERVER_URL is required');\n process.exit(1);\n }\n if (!gatewayConfig.authToken) {\n console.error('ERROR: RXDB_AUTH_TOKEN is required');\n process.exit(1);\n }\n if (!gatewayConfig.domainId) {\n console.error('ERROR: RXDB_DOMAIN_ID is required');\n process.exit(1);\n }\n\n // Log non-sensitive configuration on startup\n console.log('MCP Gateway starting with configuration:');\n console.log(` Port: ${gatewayConfig.port}`);\n console.log(` Host: ${gatewayConfig.host}`);\n console.log(` RxDB Server URL: ${gatewayConfig.rxdbServerUrl}`);\n console.log(` Domain ID: ${gatewayConfig.domainId}`);\n console.log(` Project Path: ${gatewayConfig.projectPath || 'not set'}`);\n console.log(` User ID: ${gatewayConfig.userId || 'not set'}`);\n console.log(` Org ID: ${gatewayConfig.orgId || 'not set'}`);\n console.log(` CORS Origins: ${gatewayConfig.corsOrigins?.join(', ') || 'not set'}`);\n\n const server = new MCPGatewayServer(gatewayConfig);\n\n // Graceful shutdown handler\n const gracefulShutdown = async (signal: string) => {\n if (isShuttingDown) {\n console.log('Shutdown already in progress, ignoring signal:', signal);\n return;\n }\n\n isShuttingDown = true;\n console.log(`\\nReceived ${signal}, shutting down gracefully...`);\n\n try {\n await server.close();\n console.log('Server closed successfully');\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n // Register signal handlers\n process.on('SIGINT', () => gracefulShutdown('SIGINT'));\n process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));\n\n try {\n await server.start();\n } catch (error) {\n console.error('Failed to start MCP Gateway:', error);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":"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","names":["resolve","config","express","cors","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/InMemoryTransport.ts","../src/cli.ts"],"sourcesContent":["// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport express, { Express, Request, Response } from 'express'\nimport cors from 'cors'\nimport { FlowStateMCPServer } from '@epicdm/flowstate-mcp'\nimport { InMemoryTransport } from './InMemoryTransport'\nimport type { MCPGatewayConfig, ToolCallRequest, ToolCallResponse } from './types'\n\nexport class MCPGatewayServer {\n private app: Express\n private mcpServer: FlowStateMCPServer\n private config: MCPGatewayConfig\n private isInitialized = false\n private httpServer?: ReturnType<Express['listen']>\n private transport?: InMemoryTransport\n\n /**\n * Serialize {token-swap → sendRequest} sequences so concurrent dev\n * requests can't race each other. Before Phase C this wrapper mutated\n * `mcpServer.config.authToken` without any lock, so a slow request\n * could pick up the token a faster concurrent request had just\n * written — a classic confused-deputy inversion of privilege.\n *\n * The lock is chained: each caller awaits the previous tail, then\n * claims the next tail. Errors don't poison the chain because we\n * swallow them in the tail promise (callers still see their own\n * error via the returned promise).\n */\n private requestChain: Promise<unknown> = Promise.resolve()\n private async withTokenLock<T>(token: string | undefined, op: () => Promise<T>): Promise<T> {\n const previous = this.requestChain\n let release!: () => void\n this.requestChain = new Promise<void>((resolve) => {\n release = resolve\n })\n try {\n await previous.catch(() => undefined)\n if (token !== undefined) {\n ;(this.mcpServer as unknown as { config: { authToken?: string } }).config.authToken = token\n }\n return await op()\n } finally {\n release()\n }\n }\n\n constructor(config: MCPGatewayConfig) {\n this.config = config\n this.app = express()\n\n // Initialize MCP Server\n this.mcpServer = new FlowStateMCPServer({\n rxdbServerUrl: config.rxdbServerUrl,\n domainId: config.domainId,\n projectPath: config.projectPath || process.cwd(),\n ...(config.userId && { userId: config.userId }),\n ...(config.orgId && { orgId: config.orgId }),\n } as any)\n\n this.setupMiddleware()\n this.setupRoutes()\n }\n\n /**\n * Get list of tools from MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for listing tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n */\n private async listTools(): Promise<any[]> {\n if (!this.transport) {\n return []\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/list' as const,\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to list tools')\n }\n\n return response.result?.tools || []\n }\n\n /**\n * Call a tool on the MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for calling tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n *\n * NOTE: The MCP SDK's server.request() method appears to require the request\n * object to be passed twice (once as the request, once as params). This seems\n * to be how the underlying MCP SDK expects the call to be structured based on\n * the protocol specification.\n */\n private async callTool(toolName: string, args: Record<string, any>): Promise<any> {\n if (!this.transport) {\n throw new Error('MCP server not available')\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/call' as const,\n params: {\n name: toolName,\n arguments: args,\n },\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to call tool')\n }\n\n return response.result?.content?.[0]?.text || response.result\n }\n\n private setupMiddleware(): void {\n // CORS\n this.app.use(\n cors({\n origin: this.config.corsOrigins || '*',\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n })\n )\n\n // Body parsing\n this.app.use(express.json())\n this.app.use(express.urlencoded({ extended: true }))\n\n // Request logging\n this.app.use((req, res, next) => {\n console.log(`${new Date().toISOString()} ${req.method} ${req.path}`)\n next()\n })\n }\n\n /**\n * Extract and forward auth token from request to MCP server\n *\n * This enables per-request auth so each API call can use its own token,\n * rather than relying on the initial config token.\n */\n /**\n * Extract a per-request bearer token from the Authorization header.\n * Returns `undefined` on absent / malformed / unrecognised shapes so\n * `withTokenLock` leaves the existing config token in place (rather\n * than clobbering it with garbage).\n */\n private extractRequestToken(req: Request): string | undefined {\n const authHeader = req.headers['authorization']\n if (!authHeader) return undefined\n const token = authHeader.toString().replace('Bearer ', '').trim()\n if (!token) return undefined\n if (!token.startsWith('eyJ') && !token.startsWith('epic_')) return undefined\n return token\n }\n\n private setupRoutes(): void {\n // Health check\n this.app.get('/health', (req: Request, res: Response) => {\n res.json({\n status: 'ok',\n initialized: this.isInitialized,\n timestamp: new Date().toISOString(),\n })\n })\n\n // List available tools\n this.app.get('/mcp/tools', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n error: 'Server not initialized',\n })\n }\n\n // Phase C: swap token + issue request atomically under the\n // per-request chain so concurrent requests can't inversion of\n // privilege each other.\n const tools = await this.withTokenLock(this.extractRequestToken(req), () =>\n this.listTools()\n )\n res.json({ tools })\n } catch (error) {\n console.error('Error listing tools:', error)\n res.status(500).json({\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })\n\n // Execute a tool\n this.app.post('/mcp/tools/call', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n success: false,\n error: 'Server not initialized',\n })\n }\n\n const { toolName, arguments: args } = req.body as ToolCallRequest\n\n if (!toolName) {\n return res.status(400).json({\n success: false,\n error: 'toolName is required',\n })\n }\n\n const result = await this.withTokenLock(this.extractRequestToken(req), () =>\n this.callTool(toolName, args || {})\n )\n\n const response: ToolCallResponse = {\n success: true,\n result,\n }\n\n res.json(response)\n } catch (error) {\n console.error('Error calling tool:', error)\n const response: ToolCallResponse = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n res.status(500).json(response)\n }\n })\n }\n\n async initialize(): Promise<void> {\n await this.mcpServer.initialize()\n\n // Connect the MCP server to an in-memory transport\n // This is required for the MCP SDK to handle requests\n this.transport = new InMemoryTransport()\n const server = (this.mcpServer as any).server\n if (server) {\n await server.connect(this.transport)\n }\n\n this.isInitialized = true\n console.log('MCP Gateway initialized')\n }\n\n async start(): Promise<void> {\n await this.initialize()\n\n return new Promise(resolve => {\n this.httpServer = this.app.listen(this.config.port, this.config.host, () => {\n console.log(`MCP Gateway listening on http://${this.config.host}:${this.config.port}`)\n resolve()\n })\n })\n }\n\n async close(): Promise<void> {\n // Close HTTP server first\n if (this.httpServer) {\n await new Promise<void>((resolve, reject) => {\n this.httpServer!.close(err => {\n if (err) {\n console.error('Error closing HTTP server:', err)\n reject(err)\n } else {\n console.log('HTTP server closed')\n resolve()\n }\n })\n })\n }\n\n // Then close MCP server\n await this.mcpServer.close()\n console.log('MCP Gateway closed')\n }\n}\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport type { JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, JSONRPCErrorResponse } from '@modelcontextprotocol/sdk/types.js';\nimport { EventEmitter } from 'events';\n\n/**\n * In-Memory Transport for programmatic MCP server usage\n *\n * This transport creates a bidirectional message channel that allows\n * the MCP server to be used programmatically without stdio/SSE.\n */\nexport class InMemoryTransport implements Transport {\n sessionId?: string;\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n private started = false;\n private emitter = new EventEmitter();\n private responseHandlers = new Map<string | number, (response: JSONRPCMessage) => void>();\n\n async start(): Promise<void> {\n this.started = true;\n this.sessionId = `inmemory-${Date.now()}-${Math.random().toString(36).substring(7)}`;\n }\n\n async send(message: JSONRPCMessage): Promise<void> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n // If this is a response (has 'result' or 'error' and 'id')\n if ('result' in message || 'error' in message) {\n const response = message as JSONRPCResponse | JSONRPCErrorResponse;\n const handler = this.responseHandlers.get(response.id!);\n if (handler) {\n handler(message);\n this.responseHandlers.delete(response.id!);\n }\n }\n }\n\n async close(): Promise<void> {\n this.started = false;\n this.responseHandlers.clear();\n this.onclose?.();\n }\n\n setProtocolVersion?(version: string): void {\n // No-op for in-memory transport\n }\n\n /**\n * Send a request and wait for response\n * Used by the gateway to make programmatic requests\n */\n async sendRequest(request: JSONRPCMessage): Promise<JSONRPCMessage> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n return new Promise((resolve, reject) => {\n const req = request as JSONRPCRequest;\n const timeout = setTimeout(() => {\n this.responseHandlers.delete(req.id);\n reject(new Error('Request timeout'));\n }, 60000);\n\n this.responseHandlers.set(req.id, (response) => {\n clearTimeout(timeout);\n resolve(response);\n });\n\n // Route request to server's message handler\n if (this.onmessage) {\n this.onmessage(request);\n } else {\n reject(new Error('Transport not connected to server'));\n }\n });\n }\n}\n","#!/usr/bin/env node\n// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport { MCPGatewayServer } from './server';\nimport { config } from 'dotenv';\nimport { resolve } from 'path';\n\n// Load environment variables\nconfig({ path: resolve(process.cwd(), '.env') });\n\n// Shutdown guard to prevent race conditions\nlet isShuttingDown = false;\n\n// Global error handlers\nprocess.on('unhandledRejection', (reason, promise) => {\n console.error('Unhandled Rejection at:', promise, 'reason:', reason);\n process.exit(1);\n});\n\nprocess.on('uncaughtException', (error) => {\n console.error('Uncaught Exception:', error);\n process.exit(1);\n});\n\nasync function main() {\n const gatewayConfig = {\n port: parseInt(process.env.MCP_GATEWAY_PORT || '7081', 10),\n host: process.env.MCP_GATEWAY_HOST || '0.0.0.0',\n rxdbServerUrl: process.env.RXDB_SERVER_URL || '',\n authToken: process.env.RXDB_AUTH_TOKEN || '',\n domainId: process.env.RXDB_DOMAIN_ID || '',\n projectPath: process.env.PROJECT_PATH,\n userId: process.env.USER_ID,\n orgId: process.env.ORG_ID,\n corsOrigins: process.env.CORS_ORIGINS?.split(','),\n };\n\n // Validate required config\n if (!gatewayConfig.rxdbServerUrl) {\n console.error('ERROR: RXDB_SERVER_URL is required');\n process.exit(1);\n }\n if (!gatewayConfig.authToken) {\n console.error('ERROR: RXDB_AUTH_TOKEN is required');\n process.exit(1);\n }\n if (!gatewayConfig.domainId) {\n console.error('ERROR: RXDB_DOMAIN_ID is required');\n process.exit(1);\n }\n\n // Log non-sensitive configuration on startup\n console.log('MCP Gateway starting with configuration:');\n console.log(` Port: ${gatewayConfig.port}`);\n console.log(` Host: ${gatewayConfig.host}`);\n console.log(` RxDB Server URL: ${gatewayConfig.rxdbServerUrl}`);\n console.log(` Domain ID: ${gatewayConfig.domainId}`);\n console.log(` Project Path: ${gatewayConfig.projectPath || 'not set'}`);\n console.log(` User ID: ${gatewayConfig.userId || 'not set'}`);\n console.log(` Org ID: ${gatewayConfig.orgId || 'not set'}`);\n console.log(` CORS Origins: ${gatewayConfig.corsOrigins?.join(', ') || 'not set'}`);\n\n const server = new MCPGatewayServer(gatewayConfig);\n\n // Graceful shutdown handler\n const gracefulShutdown = async (signal: string) => {\n if (isShuttingDown) {\n console.log('Shutdown already in progress, ignoring signal:', signal);\n return;\n }\n\n isShuttingDown = true;\n console.log(`\\nReceived ${signal}, shutting down gracefully...`);\n\n try {\n await server.close();\n console.log('Server closed successfully');\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n // Register signal handlers\n process.on('SIGINT', () => gracefulShutdown('SIGINT'));\n process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));\n\n try {\n await server.start();\n } catch (error) {\n console.error('Failed to start MCP Gateway:', error);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":"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","names":["resolve","resolve","config","express","cors"]}
|
package/dist/cli.mjs
CHANGED
|
@@ -74,6 +74,36 @@ var MCPGatewayServer = class {
|
|
|
74
74
|
isInitialized = false;
|
|
75
75
|
httpServer;
|
|
76
76
|
transport;
|
|
77
|
+
/**
|
|
78
|
+
* Serialize {token-swap → sendRequest} sequences so concurrent dev
|
|
79
|
+
* requests can't race each other. Before Phase C this wrapper mutated
|
|
80
|
+
* `mcpServer.config.authToken` without any lock, so a slow request
|
|
81
|
+
* could pick up the token a faster concurrent request had just
|
|
82
|
+
* written — a classic confused-deputy inversion of privilege.
|
|
83
|
+
*
|
|
84
|
+
* The lock is chained: each caller awaits the previous tail, then
|
|
85
|
+
* claims the next tail. Errors don't poison the chain because we
|
|
86
|
+
* swallow them in the tail promise (callers still see their own
|
|
87
|
+
* error via the returned promise).
|
|
88
|
+
*/
|
|
89
|
+
requestChain = Promise.resolve();
|
|
90
|
+
async withTokenLock(token, op) {
|
|
91
|
+
const previous = this.requestChain;
|
|
92
|
+
let release;
|
|
93
|
+
this.requestChain = new Promise((resolve2) => {
|
|
94
|
+
release = resolve2;
|
|
95
|
+
});
|
|
96
|
+
try {
|
|
97
|
+
await previous.catch(() => void 0);
|
|
98
|
+
if (token !== void 0) {
|
|
99
|
+
;
|
|
100
|
+
this.mcpServer.config.authToken = token;
|
|
101
|
+
}
|
|
102
|
+
return await op();
|
|
103
|
+
} finally {
|
|
104
|
+
release();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
77
107
|
constructor(config2) {
|
|
78
108
|
this.config = config2;
|
|
79
109
|
this.app = express();
|
|
@@ -177,28 +207,19 @@ var MCPGatewayServer = class {
|
|
|
177
207
|
* This enables per-request auth so each API call can use its own token,
|
|
178
208
|
* rather than relying on the initial config token.
|
|
179
209
|
*/
|
|
180
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Extract a per-request bearer token from the Authorization header.
|
|
212
|
+
* Returns `undefined` on absent / malformed / unrecognised shapes so
|
|
213
|
+
* `withTokenLock` leaves the existing config token in place (rather
|
|
214
|
+
* than clobbering it with garbage).
|
|
215
|
+
*/
|
|
216
|
+
extractRequestToken(req) {
|
|
181
217
|
const authHeader = req.headers["authorization"];
|
|
182
|
-
if (!authHeader)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (!token) {
|
|
188
|
-
console.log("[MCP Gateway] Empty token after Bearer prefix removal");
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
if (!token.startsWith("eyJ") && !token.startsWith("epic_")) {
|
|
192
|
-
console.log(`[MCP Gateway] Invalid token format (starts with: ${token.substring(0, 10)}...)`);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
;
|
|
197
|
-
this.mcpServer.config.authToken = token;
|
|
198
|
-
console.log("[MCP Gateway] Auth token updated from request");
|
|
199
|
-
} catch (error) {
|
|
200
|
-
console.error("[MCP Gateway] Failed to update auth config:", error);
|
|
201
|
-
}
|
|
218
|
+
if (!authHeader) return void 0;
|
|
219
|
+
const token = authHeader.toString().replace("Bearer ", "").trim();
|
|
220
|
+
if (!token) return void 0;
|
|
221
|
+
if (!token.startsWith("eyJ") && !token.startsWith("epic_")) return void 0;
|
|
222
|
+
return token;
|
|
202
223
|
}
|
|
203
224
|
setupRoutes() {
|
|
204
225
|
this.app.get("/health", (req, res) => {
|
|
@@ -215,8 +236,10 @@ var MCPGatewayServer = class {
|
|
|
215
236
|
error: "Server not initialized"
|
|
216
237
|
});
|
|
217
238
|
}
|
|
218
|
-
this.
|
|
219
|
-
|
|
239
|
+
const tools = await this.withTokenLock(
|
|
240
|
+
this.extractRequestToken(req),
|
|
241
|
+
() => this.listTools()
|
|
242
|
+
);
|
|
220
243
|
res.json({ tools });
|
|
221
244
|
} catch (error) {
|
|
222
245
|
console.error("Error listing tools:", error);
|
|
@@ -233,7 +256,6 @@ var MCPGatewayServer = class {
|
|
|
233
256
|
error: "Server not initialized"
|
|
234
257
|
});
|
|
235
258
|
}
|
|
236
|
-
this.updateAuthFromRequest(req);
|
|
237
259
|
const { toolName, arguments: args } = req.body;
|
|
238
260
|
if (!toolName) {
|
|
239
261
|
return res.status(400).json({
|
|
@@ -241,7 +263,10 @@ var MCPGatewayServer = class {
|
|
|
241
263
|
error: "toolName is required"
|
|
242
264
|
});
|
|
243
265
|
}
|
|
244
|
-
const result = await this.
|
|
266
|
+
const result = await this.withTokenLock(
|
|
267
|
+
this.extractRequestToken(req),
|
|
268
|
+
() => this.callTool(toolName, args || {})
|
|
269
|
+
);
|
|
245
270
|
const response = {
|
|
246
271
|
success: true,
|
|
247
272
|
result
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/InMemoryTransport.ts","../src/cli.ts"],"sourcesContent":["// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport express, { Express, Request, Response } from 'express'\nimport cors from 'cors'\nimport { FlowStateMCPServer } from '@epicdm/flowstate-mcp'\nimport { InMemoryTransport } from './InMemoryTransport'\nimport type { MCPGatewayConfig, ToolCallRequest, ToolCallResponse } from './types'\n\nexport class MCPGatewayServer {\n private app: Express\n private mcpServer: FlowStateMCPServer\n private config: MCPGatewayConfig\n private isInitialized = false\n private httpServer?: ReturnType<Express['listen']>\n private transport?: InMemoryTransport\n\n constructor(config: MCPGatewayConfig) {\n this.config = config\n this.app = express()\n\n // Initialize MCP Server\n this.mcpServer = new FlowStateMCPServer({\n rxdbServerUrl: config.rxdbServerUrl,\n domainId: config.domainId,\n projectPath: config.projectPath || process.cwd(),\n ...(config.userId && { userId: config.userId }),\n ...(config.orgId && { orgId: config.orgId }),\n } as any)\n\n this.setupMiddleware()\n this.setupRoutes()\n }\n\n /**\n * Get list of tools from MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for listing tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n */\n private async listTools(): Promise<any[]> {\n if (!this.transport) {\n return []\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/list' as const,\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to list tools')\n }\n\n return response.result?.tools || []\n }\n\n /**\n * Call a tool on the MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for calling tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n *\n * NOTE: The MCP SDK's server.request() method appears to require the request\n * object to be passed twice (once as the request, once as params). This seems\n * to be how the underlying MCP SDK expects the call to be structured based on\n * the protocol specification.\n */\n private async callTool(toolName: string, args: Record<string, any>): Promise<any> {\n if (!this.transport) {\n throw new Error('MCP server not available')\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/call' as const,\n params: {\n name: toolName,\n arguments: args,\n },\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to call tool')\n }\n\n return response.result?.content?.[0]?.text || response.result\n }\n\n private setupMiddleware(): void {\n // CORS\n this.app.use(\n cors({\n origin: this.config.corsOrigins || '*',\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n })\n )\n\n // Body parsing\n this.app.use(express.json())\n this.app.use(express.urlencoded({ extended: true }))\n\n // Request logging\n this.app.use((req, res, next) => {\n console.log(`${new Date().toISOString()} ${req.method} ${req.path}`)\n next()\n })\n }\n\n /**\n * Extract and forward auth token from request to MCP server\n *\n * This enables per-request auth so each API call can use its own token,\n * rather than relying on the initial config token.\n */\n private updateAuthFromRequest(req: Request): void {\n const authHeader = req.headers['authorization']\n if (!authHeader) {\n console.log('[MCP Gateway] No Authorization header in request')\n return\n }\n\n const token = authHeader.toString().replace('Bearer ', '')\n if (!token) {\n console.log('[MCP Gateway] Empty token after Bearer prefix removal')\n return\n }\n\n // Only accept valid-looking tokens (JWT or API tokens)\n if (!token.startsWith('eyJ') && !token.startsWith('epic_')) {\n console.log(`[MCP Gateway] Invalid token format (starts with: ${token.substring(0, 10)}...)`)\n return\n }\n\n try {\n ;(this.mcpServer as any).config.authToken = token\n console.log('[MCP Gateway] Auth token updated from request')\n } catch (error) {\n console.error('[MCP Gateway] Failed to update auth config:', error)\n }\n }\n\n private setupRoutes(): void {\n // Health check\n this.app.get('/health', (req: Request, res: Response) => {\n res.json({\n status: 'ok',\n initialized: this.isInitialized,\n timestamp: new Date().toISOString(),\n })\n })\n\n // List available tools\n this.app.get('/mcp/tools', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n error: 'Server not initialized',\n })\n }\n\n // Forward per-request auth to MCP server\n this.updateAuthFromRequest(req)\n\n const tools = await this.listTools()\n res.json({ tools })\n } catch (error) {\n console.error('Error listing tools:', error)\n res.status(500).json({\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })\n\n // Execute a tool\n this.app.post('/mcp/tools/call', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n success: false,\n error: 'Server not initialized',\n })\n }\n\n // Forward per-request auth to MCP server\n this.updateAuthFromRequest(req)\n\n const { toolName, arguments: args } = req.body as ToolCallRequest\n\n if (!toolName) {\n return res.status(400).json({\n success: false,\n error: 'toolName is required',\n })\n }\n\n const result = await this.callTool(toolName, args || {})\n\n const response: ToolCallResponse = {\n success: true,\n result,\n }\n\n res.json(response)\n } catch (error) {\n console.error('Error calling tool:', error)\n const response: ToolCallResponse = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n res.status(500).json(response)\n }\n })\n }\n\n async initialize(): Promise<void> {\n await this.mcpServer.initialize()\n\n // Connect the MCP server to an in-memory transport\n // This is required for the MCP SDK to handle requests\n this.transport = new InMemoryTransport()\n const server = (this.mcpServer as any).server\n if (server) {\n await server.connect(this.transport)\n }\n\n this.isInitialized = true\n console.log('MCP Gateway initialized')\n }\n\n async start(): Promise<void> {\n await this.initialize()\n\n return new Promise(resolve => {\n this.httpServer = this.app.listen(this.config.port, this.config.host, () => {\n console.log(`MCP Gateway listening on http://${this.config.host}:${this.config.port}`)\n resolve()\n })\n })\n }\n\n async close(): Promise<void> {\n // Close HTTP server first\n if (this.httpServer) {\n await new Promise<void>((resolve, reject) => {\n this.httpServer!.close(err => {\n if (err) {\n console.error('Error closing HTTP server:', err)\n reject(err)\n } else {\n console.log('HTTP server closed')\n resolve()\n }\n })\n })\n }\n\n // Then close MCP server\n await this.mcpServer.close()\n console.log('MCP Gateway closed')\n }\n}\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport type { JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, JSONRPCErrorResponse } from '@modelcontextprotocol/sdk/types.js';\nimport { EventEmitter } from 'events';\n\n/**\n * In-Memory Transport for programmatic MCP server usage\n *\n * This transport creates a bidirectional message channel that allows\n * the MCP server to be used programmatically without stdio/SSE.\n */\nexport class InMemoryTransport implements Transport {\n sessionId?: string;\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n private started = false;\n private emitter = new EventEmitter();\n private responseHandlers = new Map<string | number, (response: JSONRPCMessage) => void>();\n\n async start(): Promise<void> {\n this.started = true;\n this.sessionId = `inmemory-${Date.now()}-${Math.random().toString(36).substring(7)}`;\n }\n\n async send(message: JSONRPCMessage): Promise<void> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n // If this is a response (has 'result' or 'error' and 'id')\n if ('result' in message || 'error' in message) {\n const response = message as JSONRPCResponse | JSONRPCErrorResponse;\n const handler = this.responseHandlers.get(response.id!);\n if (handler) {\n handler(message);\n this.responseHandlers.delete(response.id!);\n }\n }\n }\n\n async close(): Promise<void> {\n this.started = false;\n this.responseHandlers.clear();\n this.onclose?.();\n }\n\n setProtocolVersion?(version: string): void {\n // No-op for in-memory transport\n }\n\n /**\n * Send a request and wait for response\n * Used by the gateway to make programmatic requests\n */\n async sendRequest(request: JSONRPCMessage): Promise<JSONRPCMessage> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n return new Promise((resolve, reject) => {\n const req = request as JSONRPCRequest;\n const timeout = setTimeout(() => {\n this.responseHandlers.delete(req.id);\n reject(new Error('Request timeout'));\n }, 60000);\n\n this.responseHandlers.set(req.id, (response) => {\n clearTimeout(timeout);\n resolve(response);\n });\n\n // Route request to server's message handler\n if (this.onmessage) {\n this.onmessage(request);\n } else {\n reject(new Error('Transport not connected to server'));\n }\n });\n }\n}\n","#!/usr/bin/env node\n// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport { MCPGatewayServer } from './server';\nimport { config } from 'dotenv';\nimport { resolve } from 'path';\n\n// Load environment variables\nconfig({ path: resolve(process.cwd(), '.env') });\n\n// Shutdown guard to prevent race conditions\nlet isShuttingDown = false;\n\n// Global error handlers\nprocess.on('unhandledRejection', (reason, promise) => {\n console.error('Unhandled Rejection at:', promise, 'reason:', reason);\n process.exit(1);\n});\n\nprocess.on('uncaughtException', (error) => {\n console.error('Uncaught Exception:', error);\n process.exit(1);\n});\n\nasync function main() {\n const gatewayConfig = {\n port: parseInt(process.env.MCP_GATEWAY_PORT || '7081', 10),\n host: process.env.MCP_GATEWAY_HOST || '0.0.0.0',\n rxdbServerUrl: process.env.RXDB_SERVER_URL || '',\n authToken: process.env.RXDB_AUTH_TOKEN || '',\n domainId: process.env.RXDB_DOMAIN_ID || '',\n projectPath: process.env.PROJECT_PATH,\n userId: process.env.USER_ID,\n orgId: process.env.ORG_ID,\n corsOrigins: process.env.CORS_ORIGINS?.split(','),\n };\n\n // Validate required config\n if (!gatewayConfig.rxdbServerUrl) {\n console.error('ERROR: RXDB_SERVER_URL is required');\n process.exit(1);\n }\n if (!gatewayConfig.authToken) {\n console.error('ERROR: RXDB_AUTH_TOKEN is required');\n process.exit(1);\n }\n if (!gatewayConfig.domainId) {\n console.error('ERROR: RXDB_DOMAIN_ID is required');\n process.exit(1);\n }\n\n // Log non-sensitive configuration on startup\n console.log('MCP Gateway starting with configuration:');\n console.log(` Port: ${gatewayConfig.port}`);\n console.log(` Host: ${gatewayConfig.host}`);\n console.log(` RxDB Server URL: ${gatewayConfig.rxdbServerUrl}`);\n console.log(` Domain ID: ${gatewayConfig.domainId}`);\n console.log(` Project Path: ${gatewayConfig.projectPath || 'not set'}`);\n console.log(` User ID: ${gatewayConfig.userId || 'not set'}`);\n console.log(` Org ID: ${gatewayConfig.orgId || 'not set'}`);\n console.log(` CORS Origins: ${gatewayConfig.corsOrigins?.join(', ') || 'not set'}`);\n\n const server = new MCPGatewayServer(gatewayConfig);\n\n // Graceful shutdown handler\n const gracefulShutdown = async (signal: string) => {\n if (isShuttingDown) {\n console.log('Shutdown already in progress, ignoring signal:', signal);\n return;\n }\n\n isShuttingDown = true;\n console.log(`\\nReceived ${signal}, shutting down gracefully...`);\n\n try {\n await server.close();\n console.log('Server closed successfully');\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n // Register signal handlers\n process.on('SIGINT', () => gracefulShutdown('SIGINT'));\n process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));\n\n try {\n await server.start();\n } catch (error) {\n console.error('Failed to start MCP Gateway:', error);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":"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","names":["resolve","config","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/InMemoryTransport.ts","../src/cli.ts"],"sourcesContent":["// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport express, { Express, Request, Response } from 'express'\nimport cors from 'cors'\nimport { FlowStateMCPServer } from '@epicdm/flowstate-mcp'\nimport { InMemoryTransport } from './InMemoryTransport'\nimport type { MCPGatewayConfig, ToolCallRequest, ToolCallResponse } from './types'\n\nexport class MCPGatewayServer {\n private app: Express\n private mcpServer: FlowStateMCPServer\n private config: MCPGatewayConfig\n private isInitialized = false\n private httpServer?: ReturnType<Express['listen']>\n private transport?: InMemoryTransport\n\n /**\n * Serialize {token-swap → sendRequest} sequences so concurrent dev\n * requests can't race each other. Before Phase C this wrapper mutated\n * `mcpServer.config.authToken` without any lock, so a slow request\n * could pick up the token a faster concurrent request had just\n * written — a classic confused-deputy inversion of privilege.\n *\n * The lock is chained: each caller awaits the previous tail, then\n * claims the next tail. Errors don't poison the chain because we\n * swallow them in the tail promise (callers still see their own\n * error via the returned promise).\n */\n private requestChain: Promise<unknown> = Promise.resolve()\n private async withTokenLock<T>(token: string | undefined, op: () => Promise<T>): Promise<T> {\n const previous = this.requestChain\n let release!: () => void\n this.requestChain = new Promise<void>((resolve) => {\n release = resolve\n })\n try {\n await previous.catch(() => undefined)\n if (token !== undefined) {\n ;(this.mcpServer as unknown as { config: { authToken?: string } }).config.authToken = token\n }\n return await op()\n } finally {\n release()\n }\n }\n\n constructor(config: MCPGatewayConfig) {\n this.config = config\n this.app = express()\n\n // Initialize MCP Server\n this.mcpServer = new FlowStateMCPServer({\n rxdbServerUrl: config.rxdbServerUrl,\n domainId: config.domainId,\n projectPath: config.projectPath || process.cwd(),\n ...(config.userId && { userId: config.userId }),\n ...(config.orgId && { orgId: config.orgId }),\n } as any)\n\n this.setupMiddleware()\n this.setupRoutes()\n }\n\n /**\n * Get list of tools from MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for listing tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n */\n private async listTools(): Promise<any[]> {\n if (!this.transport) {\n return []\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/list' as const,\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to list tools')\n }\n\n return response.result?.tools || []\n }\n\n /**\n * Call a tool on the MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for calling tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n *\n * NOTE: The MCP SDK's server.request() method appears to require the request\n * object to be passed twice (once as the request, once as params). This seems\n * to be how the underlying MCP SDK expects the call to be structured based on\n * the protocol specification.\n */\n private async callTool(toolName: string, args: Record<string, any>): Promise<any> {\n if (!this.transport) {\n throw new Error('MCP server not available')\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/call' as const,\n params: {\n name: toolName,\n arguments: args,\n },\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to call tool')\n }\n\n return response.result?.content?.[0]?.text || response.result\n }\n\n private setupMiddleware(): void {\n // CORS\n this.app.use(\n cors({\n origin: this.config.corsOrigins || '*',\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n })\n )\n\n // Body parsing\n this.app.use(express.json())\n this.app.use(express.urlencoded({ extended: true }))\n\n // Request logging\n this.app.use((req, res, next) => {\n console.log(`${new Date().toISOString()} ${req.method} ${req.path}`)\n next()\n })\n }\n\n /**\n * Extract and forward auth token from request to MCP server\n *\n * This enables per-request auth so each API call can use its own token,\n * rather than relying on the initial config token.\n */\n /**\n * Extract a per-request bearer token from the Authorization header.\n * Returns `undefined` on absent / malformed / unrecognised shapes so\n * `withTokenLock` leaves the existing config token in place (rather\n * than clobbering it with garbage).\n */\n private extractRequestToken(req: Request): string | undefined {\n const authHeader = req.headers['authorization']\n if (!authHeader) return undefined\n const token = authHeader.toString().replace('Bearer ', '').trim()\n if (!token) return undefined\n if (!token.startsWith('eyJ') && !token.startsWith('epic_')) return undefined\n return token\n }\n\n private setupRoutes(): void {\n // Health check\n this.app.get('/health', (req: Request, res: Response) => {\n res.json({\n status: 'ok',\n initialized: this.isInitialized,\n timestamp: new Date().toISOString(),\n })\n })\n\n // List available tools\n this.app.get('/mcp/tools', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n error: 'Server not initialized',\n })\n }\n\n // Phase C: swap token + issue request atomically under the\n // per-request chain so concurrent requests can't inversion of\n // privilege each other.\n const tools = await this.withTokenLock(this.extractRequestToken(req), () =>\n this.listTools()\n )\n res.json({ tools })\n } catch (error) {\n console.error('Error listing tools:', error)\n res.status(500).json({\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })\n\n // Execute a tool\n this.app.post('/mcp/tools/call', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n success: false,\n error: 'Server not initialized',\n })\n }\n\n const { toolName, arguments: args } = req.body as ToolCallRequest\n\n if (!toolName) {\n return res.status(400).json({\n success: false,\n error: 'toolName is required',\n })\n }\n\n const result = await this.withTokenLock(this.extractRequestToken(req), () =>\n this.callTool(toolName, args || {})\n )\n\n const response: ToolCallResponse = {\n success: true,\n result,\n }\n\n res.json(response)\n } catch (error) {\n console.error('Error calling tool:', error)\n const response: ToolCallResponse = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n res.status(500).json(response)\n }\n })\n }\n\n async initialize(): Promise<void> {\n await this.mcpServer.initialize()\n\n // Connect the MCP server to an in-memory transport\n // This is required for the MCP SDK to handle requests\n this.transport = new InMemoryTransport()\n const server = (this.mcpServer as any).server\n if (server) {\n await server.connect(this.transport)\n }\n\n this.isInitialized = true\n console.log('MCP Gateway initialized')\n }\n\n async start(): Promise<void> {\n await this.initialize()\n\n return new Promise(resolve => {\n this.httpServer = this.app.listen(this.config.port, this.config.host, () => {\n console.log(`MCP Gateway listening on http://${this.config.host}:${this.config.port}`)\n resolve()\n })\n })\n }\n\n async close(): Promise<void> {\n // Close HTTP server first\n if (this.httpServer) {\n await new Promise<void>((resolve, reject) => {\n this.httpServer!.close(err => {\n if (err) {\n console.error('Error closing HTTP server:', err)\n reject(err)\n } else {\n console.log('HTTP server closed')\n resolve()\n }\n })\n })\n }\n\n // Then close MCP server\n await this.mcpServer.close()\n console.log('MCP Gateway closed')\n }\n}\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport type { JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, JSONRPCErrorResponse } from '@modelcontextprotocol/sdk/types.js';\nimport { EventEmitter } from 'events';\n\n/**\n * In-Memory Transport for programmatic MCP server usage\n *\n * This transport creates a bidirectional message channel that allows\n * the MCP server to be used programmatically without stdio/SSE.\n */\nexport class InMemoryTransport implements Transport {\n sessionId?: string;\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n private started = false;\n private emitter = new EventEmitter();\n private responseHandlers = new Map<string | number, (response: JSONRPCMessage) => void>();\n\n async start(): Promise<void> {\n this.started = true;\n this.sessionId = `inmemory-${Date.now()}-${Math.random().toString(36).substring(7)}`;\n }\n\n async send(message: JSONRPCMessage): Promise<void> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n // If this is a response (has 'result' or 'error' and 'id')\n if ('result' in message || 'error' in message) {\n const response = message as JSONRPCResponse | JSONRPCErrorResponse;\n const handler = this.responseHandlers.get(response.id!);\n if (handler) {\n handler(message);\n this.responseHandlers.delete(response.id!);\n }\n }\n }\n\n async close(): Promise<void> {\n this.started = false;\n this.responseHandlers.clear();\n this.onclose?.();\n }\n\n setProtocolVersion?(version: string): void {\n // No-op for in-memory transport\n }\n\n /**\n * Send a request and wait for response\n * Used by the gateway to make programmatic requests\n */\n async sendRequest(request: JSONRPCMessage): Promise<JSONRPCMessage> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n return new Promise((resolve, reject) => {\n const req = request as JSONRPCRequest;\n const timeout = setTimeout(() => {\n this.responseHandlers.delete(req.id);\n reject(new Error('Request timeout'));\n }, 60000);\n\n this.responseHandlers.set(req.id, (response) => {\n clearTimeout(timeout);\n resolve(response);\n });\n\n // Route request to server's message handler\n if (this.onmessage) {\n this.onmessage(request);\n } else {\n reject(new Error('Transport not connected to server'));\n }\n });\n }\n}\n","#!/usr/bin/env node\n// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport { MCPGatewayServer } from './server';\nimport { config } from 'dotenv';\nimport { resolve } from 'path';\n\n// Load environment variables\nconfig({ path: resolve(process.cwd(), '.env') });\n\n// Shutdown guard to prevent race conditions\nlet isShuttingDown = false;\n\n// Global error handlers\nprocess.on('unhandledRejection', (reason, promise) => {\n console.error('Unhandled Rejection at:', promise, 'reason:', reason);\n process.exit(1);\n});\n\nprocess.on('uncaughtException', (error) => {\n console.error('Uncaught Exception:', error);\n process.exit(1);\n});\n\nasync function main() {\n const gatewayConfig = {\n port: parseInt(process.env.MCP_GATEWAY_PORT || '7081', 10),\n host: process.env.MCP_GATEWAY_HOST || '0.0.0.0',\n rxdbServerUrl: process.env.RXDB_SERVER_URL || '',\n authToken: process.env.RXDB_AUTH_TOKEN || '',\n domainId: process.env.RXDB_DOMAIN_ID || '',\n projectPath: process.env.PROJECT_PATH,\n userId: process.env.USER_ID,\n orgId: process.env.ORG_ID,\n corsOrigins: process.env.CORS_ORIGINS?.split(','),\n };\n\n // Validate required config\n if (!gatewayConfig.rxdbServerUrl) {\n console.error('ERROR: RXDB_SERVER_URL is required');\n process.exit(1);\n }\n if (!gatewayConfig.authToken) {\n console.error('ERROR: RXDB_AUTH_TOKEN is required');\n process.exit(1);\n }\n if (!gatewayConfig.domainId) {\n console.error('ERROR: RXDB_DOMAIN_ID is required');\n process.exit(1);\n }\n\n // Log non-sensitive configuration on startup\n console.log('MCP Gateway starting with configuration:');\n console.log(` Port: ${gatewayConfig.port}`);\n console.log(` Host: ${gatewayConfig.host}`);\n console.log(` RxDB Server URL: ${gatewayConfig.rxdbServerUrl}`);\n console.log(` Domain ID: ${gatewayConfig.domainId}`);\n console.log(` Project Path: ${gatewayConfig.projectPath || 'not set'}`);\n console.log(` User ID: ${gatewayConfig.userId || 'not set'}`);\n console.log(` Org ID: ${gatewayConfig.orgId || 'not set'}`);\n console.log(` CORS Origins: ${gatewayConfig.corsOrigins?.join(', ') || 'not set'}`);\n\n const server = new MCPGatewayServer(gatewayConfig);\n\n // Graceful shutdown handler\n const gracefulShutdown = async (signal: string) => {\n if (isShuttingDown) {\n console.log('Shutdown already in progress, ignoring signal:', signal);\n return;\n }\n\n isShuttingDown = true;\n console.log(`\\nReceived ${signal}, shutting down gracefully...`);\n\n try {\n await server.close();\n console.log('Server closed successfully');\n process.exit(0);\n } catch (error) {\n console.error('Error during shutdown:', error);\n process.exit(1);\n }\n };\n\n // Register signal handlers\n process.on('SIGINT', () => gracefulShutdown('SIGINT'));\n process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));\n\n try {\n await server.start();\n } catch (error) {\n console.error('Failed to start MCP Gateway:', error);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":"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","names":["resolve","resolve","config"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -31,6 +31,20 @@ declare class MCPGatewayServer {
|
|
|
31
31
|
private isInitialized;
|
|
32
32
|
private httpServer?;
|
|
33
33
|
private transport?;
|
|
34
|
+
/**
|
|
35
|
+
* Serialize {token-swap → sendRequest} sequences so concurrent dev
|
|
36
|
+
* requests can't race each other. Before Phase C this wrapper mutated
|
|
37
|
+
* `mcpServer.config.authToken` without any lock, so a slow request
|
|
38
|
+
* could pick up the token a faster concurrent request had just
|
|
39
|
+
* written — a classic confused-deputy inversion of privilege.
|
|
40
|
+
*
|
|
41
|
+
* The lock is chained: each caller awaits the previous tail, then
|
|
42
|
+
* claims the next tail. Errors don't poison the chain because we
|
|
43
|
+
* swallow them in the tail promise (callers still see their own
|
|
44
|
+
* error via the returned promise).
|
|
45
|
+
*/
|
|
46
|
+
private requestChain;
|
|
47
|
+
private withTokenLock;
|
|
34
48
|
constructor(config: MCPGatewayConfig);
|
|
35
49
|
/**
|
|
36
50
|
* Get list of tools from MCP server
|
|
@@ -76,7 +90,13 @@ declare class MCPGatewayServer {
|
|
|
76
90
|
* This enables per-request auth so each API call can use its own token,
|
|
77
91
|
* rather than relying on the initial config token.
|
|
78
92
|
*/
|
|
79
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Extract a per-request bearer token from the Authorization header.
|
|
95
|
+
* Returns `undefined` on absent / malformed / unrecognised shapes so
|
|
96
|
+
* `withTokenLock` leaves the existing config token in place (rather
|
|
97
|
+
* than clobbering it with garbage).
|
|
98
|
+
*/
|
|
99
|
+
private extractRequestToken;
|
|
80
100
|
private setupRoutes;
|
|
81
101
|
initialize(): Promise<void>;
|
|
82
102
|
start(): Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,20 @@ declare class MCPGatewayServer {
|
|
|
31
31
|
private isInitialized;
|
|
32
32
|
private httpServer?;
|
|
33
33
|
private transport?;
|
|
34
|
+
/**
|
|
35
|
+
* Serialize {token-swap → sendRequest} sequences so concurrent dev
|
|
36
|
+
* requests can't race each other. Before Phase C this wrapper mutated
|
|
37
|
+
* `mcpServer.config.authToken` without any lock, so a slow request
|
|
38
|
+
* could pick up the token a faster concurrent request had just
|
|
39
|
+
* written — a classic confused-deputy inversion of privilege.
|
|
40
|
+
*
|
|
41
|
+
* The lock is chained: each caller awaits the previous tail, then
|
|
42
|
+
* claims the next tail. Errors don't poison the chain because we
|
|
43
|
+
* swallow them in the tail promise (callers still see their own
|
|
44
|
+
* error via the returned promise).
|
|
45
|
+
*/
|
|
46
|
+
private requestChain;
|
|
47
|
+
private withTokenLock;
|
|
34
48
|
constructor(config: MCPGatewayConfig);
|
|
35
49
|
/**
|
|
36
50
|
* Get list of tools from MCP server
|
|
@@ -76,7 +90,13 @@ declare class MCPGatewayServer {
|
|
|
76
90
|
* This enables per-request auth so each API call can use its own token,
|
|
77
91
|
* rather than relying on the initial config token.
|
|
78
92
|
*/
|
|
79
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Extract a per-request bearer token from the Authorization header.
|
|
95
|
+
* Returns `undefined` on absent / malformed / unrecognised shapes so
|
|
96
|
+
* `withTokenLock` leaves the existing config token in place (rather
|
|
97
|
+
* than clobbering it with garbage).
|
|
98
|
+
*/
|
|
99
|
+
private extractRequestToken;
|
|
80
100
|
private setupRoutes;
|
|
81
101
|
initialize(): Promise<void>;
|
|
82
102
|
start(): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -108,6 +108,36 @@ var MCPGatewayServer = class {
|
|
|
108
108
|
isInitialized = false;
|
|
109
109
|
httpServer;
|
|
110
110
|
transport;
|
|
111
|
+
/**
|
|
112
|
+
* Serialize {token-swap → sendRequest} sequences so concurrent dev
|
|
113
|
+
* requests can't race each other. Before Phase C this wrapper mutated
|
|
114
|
+
* `mcpServer.config.authToken` without any lock, so a slow request
|
|
115
|
+
* could pick up the token a faster concurrent request had just
|
|
116
|
+
* written — a classic confused-deputy inversion of privilege.
|
|
117
|
+
*
|
|
118
|
+
* The lock is chained: each caller awaits the previous tail, then
|
|
119
|
+
* claims the next tail. Errors don't poison the chain because we
|
|
120
|
+
* swallow them in the tail promise (callers still see their own
|
|
121
|
+
* error via the returned promise).
|
|
122
|
+
*/
|
|
123
|
+
requestChain = Promise.resolve();
|
|
124
|
+
async withTokenLock(token, op) {
|
|
125
|
+
const previous = this.requestChain;
|
|
126
|
+
let release;
|
|
127
|
+
this.requestChain = new Promise((resolve) => {
|
|
128
|
+
release = resolve;
|
|
129
|
+
});
|
|
130
|
+
try {
|
|
131
|
+
await previous.catch(() => void 0);
|
|
132
|
+
if (token !== void 0) {
|
|
133
|
+
;
|
|
134
|
+
this.mcpServer.config.authToken = token;
|
|
135
|
+
}
|
|
136
|
+
return await op();
|
|
137
|
+
} finally {
|
|
138
|
+
release();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
111
141
|
constructor(config) {
|
|
112
142
|
this.config = config;
|
|
113
143
|
this.app = (0, import_express.default)();
|
|
@@ -211,28 +241,19 @@ var MCPGatewayServer = class {
|
|
|
211
241
|
* This enables per-request auth so each API call can use its own token,
|
|
212
242
|
* rather than relying on the initial config token.
|
|
213
243
|
*/
|
|
214
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Extract a per-request bearer token from the Authorization header.
|
|
246
|
+
* Returns `undefined` on absent / malformed / unrecognised shapes so
|
|
247
|
+
* `withTokenLock` leaves the existing config token in place (rather
|
|
248
|
+
* than clobbering it with garbage).
|
|
249
|
+
*/
|
|
250
|
+
extractRequestToken(req) {
|
|
215
251
|
const authHeader = req.headers["authorization"];
|
|
216
|
-
if (!authHeader)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (!token) {
|
|
222
|
-
console.log("[MCP Gateway] Empty token after Bearer prefix removal");
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
if (!token.startsWith("eyJ") && !token.startsWith("epic_")) {
|
|
226
|
-
console.log(`[MCP Gateway] Invalid token format (starts with: ${token.substring(0, 10)}...)`);
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
try {
|
|
230
|
-
;
|
|
231
|
-
this.mcpServer.config.authToken = token;
|
|
232
|
-
console.log("[MCP Gateway] Auth token updated from request");
|
|
233
|
-
} catch (error) {
|
|
234
|
-
console.error("[MCP Gateway] Failed to update auth config:", error);
|
|
235
|
-
}
|
|
252
|
+
if (!authHeader) return void 0;
|
|
253
|
+
const token = authHeader.toString().replace("Bearer ", "").trim();
|
|
254
|
+
if (!token) return void 0;
|
|
255
|
+
if (!token.startsWith("eyJ") && !token.startsWith("epic_")) return void 0;
|
|
256
|
+
return token;
|
|
236
257
|
}
|
|
237
258
|
setupRoutes() {
|
|
238
259
|
this.app.get("/health", (req, res) => {
|
|
@@ -249,8 +270,10 @@ var MCPGatewayServer = class {
|
|
|
249
270
|
error: "Server not initialized"
|
|
250
271
|
});
|
|
251
272
|
}
|
|
252
|
-
this.
|
|
253
|
-
|
|
273
|
+
const tools = await this.withTokenLock(
|
|
274
|
+
this.extractRequestToken(req),
|
|
275
|
+
() => this.listTools()
|
|
276
|
+
);
|
|
254
277
|
res.json({ tools });
|
|
255
278
|
} catch (error) {
|
|
256
279
|
console.error("Error listing tools:", error);
|
|
@@ -267,7 +290,6 @@ var MCPGatewayServer = class {
|
|
|
267
290
|
error: "Server not initialized"
|
|
268
291
|
});
|
|
269
292
|
}
|
|
270
|
-
this.updateAuthFromRequest(req);
|
|
271
293
|
const { toolName, arguments: args } = req.body;
|
|
272
294
|
if (!toolName) {
|
|
273
295
|
return res.status(400).json({
|
|
@@ -275,7 +297,10 @@ var MCPGatewayServer = class {
|
|
|
275
297
|
error: "toolName is required"
|
|
276
298
|
});
|
|
277
299
|
}
|
|
278
|
-
const result = await this.
|
|
300
|
+
const result = await this.withTokenLock(
|
|
301
|
+
this.extractRequestToken(req),
|
|
302
|
+
() => this.callTool(toolName, args || {})
|
|
303
|
+
);
|
|
279
304
|
const response = {
|
|
280
305
|
success: true,
|
|
281
306
|
result
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/InMemoryTransport.ts"],"sourcesContent":["// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nexport { MCPGatewayServer } from './server';\nexport type {\n MCPGatewayConfig,\n ToolDefinition,\n ToolCallRequest,\n ToolCallResponse,\n} from './types';\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport express, { Express, Request, Response } from 'express'\nimport cors from 'cors'\nimport { FlowStateMCPServer } from '@epicdm/flowstate-mcp'\nimport { InMemoryTransport } from './InMemoryTransport'\nimport type { MCPGatewayConfig, ToolCallRequest, ToolCallResponse } from './types'\n\nexport class MCPGatewayServer {\n private app: Express\n private mcpServer: FlowStateMCPServer\n private config: MCPGatewayConfig\n private isInitialized = false\n private httpServer?: ReturnType<Express['listen']>\n private transport?: InMemoryTransport\n\n constructor(config: MCPGatewayConfig) {\n this.config = config\n this.app = express()\n\n // Initialize MCP Server\n this.mcpServer = new FlowStateMCPServer({\n rxdbServerUrl: config.rxdbServerUrl,\n domainId: config.domainId,\n projectPath: config.projectPath || process.cwd(),\n ...(config.userId && { userId: config.userId }),\n ...(config.orgId && { orgId: config.orgId }),\n } as any)\n\n this.setupMiddleware()\n this.setupRoutes()\n }\n\n /**\n * Get list of tools from MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for listing tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n */\n private async listTools(): Promise<any[]> {\n if (!this.transport) {\n return []\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/list' as const,\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to list tools')\n }\n\n return response.result?.tools || []\n }\n\n /**\n * Call a tool on the MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for calling tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n *\n * NOTE: The MCP SDK's server.request() method appears to require the request\n * object to be passed twice (once as the request, once as params). This seems\n * to be how the underlying MCP SDK expects the call to be structured based on\n * the protocol specification.\n */\n private async callTool(toolName: string, args: Record<string, any>): Promise<any> {\n if (!this.transport) {\n throw new Error('MCP server not available')\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/call' as const,\n params: {\n name: toolName,\n arguments: args,\n },\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to call tool')\n }\n\n return response.result?.content?.[0]?.text || response.result\n }\n\n private setupMiddleware(): void {\n // CORS\n this.app.use(\n cors({\n origin: this.config.corsOrigins || '*',\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n })\n )\n\n // Body parsing\n this.app.use(express.json())\n this.app.use(express.urlencoded({ extended: true }))\n\n // Request logging\n this.app.use((req, res, next) => {\n console.log(`${new Date().toISOString()} ${req.method} ${req.path}`)\n next()\n })\n }\n\n /**\n * Extract and forward auth token from request to MCP server\n *\n * This enables per-request auth so each API call can use its own token,\n * rather than relying on the initial config token.\n */\n private updateAuthFromRequest(req: Request): void {\n const authHeader = req.headers['authorization']\n if (!authHeader) {\n console.log('[MCP Gateway] No Authorization header in request')\n return\n }\n\n const token = authHeader.toString().replace('Bearer ', '')\n if (!token) {\n console.log('[MCP Gateway] Empty token after Bearer prefix removal')\n return\n }\n\n // Only accept valid-looking tokens (JWT or API tokens)\n if (!token.startsWith('eyJ') && !token.startsWith('epic_')) {\n console.log(`[MCP Gateway] Invalid token format (starts with: ${token.substring(0, 10)}...)`)\n return\n }\n\n try {\n ;(this.mcpServer as any).config.authToken = token\n console.log('[MCP Gateway] Auth token updated from request')\n } catch (error) {\n console.error('[MCP Gateway] Failed to update auth config:', error)\n }\n }\n\n private setupRoutes(): void {\n // Health check\n this.app.get('/health', (req: Request, res: Response) => {\n res.json({\n status: 'ok',\n initialized: this.isInitialized,\n timestamp: new Date().toISOString(),\n })\n })\n\n // List available tools\n this.app.get('/mcp/tools', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n error: 'Server not initialized',\n })\n }\n\n // Forward per-request auth to MCP server\n this.updateAuthFromRequest(req)\n\n const tools = await this.listTools()\n res.json({ tools })\n } catch (error) {\n console.error('Error listing tools:', error)\n res.status(500).json({\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })\n\n // Execute a tool\n this.app.post('/mcp/tools/call', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n success: false,\n error: 'Server not initialized',\n })\n }\n\n // Forward per-request auth to MCP server\n this.updateAuthFromRequest(req)\n\n const { toolName, arguments: args } = req.body as ToolCallRequest\n\n if (!toolName) {\n return res.status(400).json({\n success: false,\n error: 'toolName is required',\n })\n }\n\n const result = await this.callTool(toolName, args || {})\n\n const response: ToolCallResponse = {\n success: true,\n result,\n }\n\n res.json(response)\n } catch (error) {\n console.error('Error calling tool:', error)\n const response: ToolCallResponse = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n res.status(500).json(response)\n }\n })\n }\n\n async initialize(): Promise<void> {\n await this.mcpServer.initialize()\n\n // Connect the MCP server to an in-memory transport\n // This is required for the MCP SDK to handle requests\n this.transport = new InMemoryTransport()\n const server = (this.mcpServer as any).server\n if (server) {\n await server.connect(this.transport)\n }\n\n this.isInitialized = true\n console.log('MCP Gateway initialized')\n }\n\n async start(): Promise<void> {\n await this.initialize()\n\n return new Promise(resolve => {\n this.httpServer = this.app.listen(this.config.port, this.config.host, () => {\n console.log(`MCP Gateway listening on http://${this.config.host}:${this.config.port}`)\n resolve()\n })\n })\n }\n\n async close(): Promise<void> {\n // Close HTTP server first\n if (this.httpServer) {\n await new Promise<void>((resolve, reject) => {\n this.httpServer!.close(err => {\n if (err) {\n console.error('Error closing HTTP server:', err)\n reject(err)\n } else {\n console.log('HTTP server closed')\n resolve()\n }\n })\n })\n }\n\n // Then close MCP server\n await this.mcpServer.close()\n console.log('MCP Gateway closed')\n }\n}\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport type { JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, JSONRPCErrorResponse } from '@modelcontextprotocol/sdk/types.js';\nimport { EventEmitter } from 'events';\n\n/**\n * In-Memory Transport for programmatic MCP server usage\n *\n * This transport creates a bidirectional message channel that allows\n * the MCP server to be used programmatically without stdio/SSE.\n */\nexport class InMemoryTransport implements Transport {\n sessionId?: string;\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n private started = false;\n private emitter = new EventEmitter();\n private responseHandlers = new Map<string | number, (response: JSONRPCMessage) => void>();\n\n async start(): Promise<void> {\n this.started = true;\n this.sessionId = `inmemory-${Date.now()}-${Math.random().toString(36).substring(7)}`;\n }\n\n async send(message: JSONRPCMessage): Promise<void> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n // If this is a response (has 'result' or 'error' and 'id')\n if ('result' in message || 'error' in message) {\n const response = message as JSONRPCResponse | JSONRPCErrorResponse;\n const handler = this.responseHandlers.get(response.id!);\n if (handler) {\n handler(message);\n this.responseHandlers.delete(response.id!);\n }\n }\n }\n\n async close(): Promise<void> {\n this.started = false;\n this.responseHandlers.clear();\n this.onclose?.();\n }\n\n setProtocolVersion?(version: string): void {\n // No-op for in-memory transport\n }\n\n /**\n * Send a request and wait for response\n * Used by the gateway to make programmatic requests\n */\n async sendRequest(request: JSONRPCMessage): Promise<JSONRPCMessage> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n return new Promise((resolve, reject) => {\n const req = request as JSONRPCRequest;\n const timeout = setTimeout(() => {\n this.responseHandlers.delete(req.id);\n reject(new Error('Request timeout'));\n }, 60000);\n\n this.responseHandlers.set(req.id, (response) => {\n clearTimeout(timeout);\n resolve(response);\n });\n\n // Route request to server's message handler\n if (this.onmessage) {\n this.onmessage(request);\n } else {\n reject(new Error('Transport not connected to server'));\n }\n });\n }\n}\n"],"mappings":"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","names":["express","cors"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/InMemoryTransport.ts"],"sourcesContent":["// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * @local-only HTTP gateway for local MCP tool execution. Cloud equivalent\n * uses worker-mcp with Workers for Platforms for per-tenant isolation and\n * service bindings for zero-overhead downstream calls. This gateway runs as\n * a single-tenant process in the Core Docker tier, routing tool calls to\n * local Docker services via HTTP.\n *\n * @module @epicdm/flowstate-mcp-gateway\n */\n\nexport { MCPGatewayServer } from './server';\nexport type {\n MCPGatewayConfig,\n ToolDefinition,\n ToolCallRequest,\n ToolCallResponse,\n} from './types';\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport express, { Express, Request, Response } from 'express'\nimport cors from 'cors'\nimport { FlowStateMCPServer } from '@epicdm/flowstate-mcp'\nimport { InMemoryTransport } from './InMemoryTransport'\nimport type { MCPGatewayConfig, ToolCallRequest, ToolCallResponse } from './types'\n\nexport class MCPGatewayServer {\n private app: Express\n private mcpServer: FlowStateMCPServer\n private config: MCPGatewayConfig\n private isInitialized = false\n private httpServer?: ReturnType<Express['listen']>\n private transport?: InMemoryTransport\n\n /**\n * Serialize {token-swap → sendRequest} sequences so concurrent dev\n * requests can't race each other. Before Phase C this wrapper mutated\n * `mcpServer.config.authToken` without any lock, so a slow request\n * could pick up the token a faster concurrent request had just\n * written — a classic confused-deputy inversion of privilege.\n *\n * The lock is chained: each caller awaits the previous tail, then\n * claims the next tail. Errors don't poison the chain because we\n * swallow them in the tail promise (callers still see their own\n * error via the returned promise).\n */\n private requestChain: Promise<unknown> = Promise.resolve()\n private async withTokenLock<T>(token: string | undefined, op: () => Promise<T>): Promise<T> {\n const previous = this.requestChain\n let release!: () => void\n this.requestChain = new Promise<void>((resolve) => {\n release = resolve\n })\n try {\n await previous.catch(() => undefined)\n if (token !== undefined) {\n ;(this.mcpServer as unknown as { config: { authToken?: string } }).config.authToken = token\n }\n return await op()\n } finally {\n release()\n }\n }\n\n constructor(config: MCPGatewayConfig) {\n this.config = config\n this.app = express()\n\n // Initialize MCP Server\n this.mcpServer = new FlowStateMCPServer({\n rxdbServerUrl: config.rxdbServerUrl,\n domainId: config.domainId,\n projectPath: config.projectPath || process.cwd(),\n ...(config.userId && { userId: config.userId }),\n ...(config.orgId && { orgId: config.orgId }),\n } as any)\n\n this.setupMiddleware()\n this.setupRoutes()\n }\n\n /**\n * Get list of tools from MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for listing tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n */\n private async listTools(): Promise<any[]> {\n if (!this.transport) {\n return []\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/list' as const,\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to list tools')\n }\n\n return response.result?.tools || []\n }\n\n /**\n * Call a tool on the MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for calling tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n *\n * NOTE: The MCP SDK's server.request() method appears to require the request\n * object to be passed twice (once as the request, once as params). This seems\n * to be how the underlying MCP SDK expects the call to be structured based on\n * the protocol specification.\n */\n private async callTool(toolName: string, args: Record<string, any>): Promise<any> {\n if (!this.transport) {\n throw new Error('MCP server not available')\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/call' as const,\n params: {\n name: toolName,\n arguments: args,\n },\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to call tool')\n }\n\n return response.result?.content?.[0]?.text || response.result\n }\n\n private setupMiddleware(): void {\n // CORS\n this.app.use(\n cors({\n origin: this.config.corsOrigins || '*',\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n })\n )\n\n // Body parsing\n this.app.use(express.json())\n this.app.use(express.urlencoded({ extended: true }))\n\n // Request logging\n this.app.use((req, res, next) => {\n console.log(`${new Date().toISOString()} ${req.method} ${req.path}`)\n next()\n })\n }\n\n /**\n * Extract and forward auth token from request to MCP server\n *\n * This enables per-request auth so each API call can use its own token,\n * rather than relying on the initial config token.\n */\n /**\n * Extract a per-request bearer token from the Authorization header.\n * Returns `undefined` on absent / malformed / unrecognised shapes so\n * `withTokenLock` leaves the existing config token in place (rather\n * than clobbering it with garbage).\n */\n private extractRequestToken(req: Request): string | undefined {\n const authHeader = req.headers['authorization']\n if (!authHeader) return undefined\n const token = authHeader.toString().replace('Bearer ', '').trim()\n if (!token) return undefined\n if (!token.startsWith('eyJ') && !token.startsWith('epic_')) return undefined\n return token\n }\n\n private setupRoutes(): void {\n // Health check\n this.app.get('/health', (req: Request, res: Response) => {\n res.json({\n status: 'ok',\n initialized: this.isInitialized,\n timestamp: new Date().toISOString(),\n })\n })\n\n // List available tools\n this.app.get('/mcp/tools', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n error: 'Server not initialized',\n })\n }\n\n // Phase C: swap token + issue request atomically under the\n // per-request chain so concurrent requests can't inversion of\n // privilege each other.\n const tools = await this.withTokenLock(this.extractRequestToken(req), () =>\n this.listTools()\n )\n res.json({ tools })\n } catch (error) {\n console.error('Error listing tools:', error)\n res.status(500).json({\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })\n\n // Execute a tool\n this.app.post('/mcp/tools/call', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n success: false,\n error: 'Server not initialized',\n })\n }\n\n const { toolName, arguments: args } = req.body as ToolCallRequest\n\n if (!toolName) {\n return res.status(400).json({\n success: false,\n error: 'toolName is required',\n })\n }\n\n const result = await this.withTokenLock(this.extractRequestToken(req), () =>\n this.callTool(toolName, args || {})\n )\n\n const response: ToolCallResponse = {\n success: true,\n result,\n }\n\n res.json(response)\n } catch (error) {\n console.error('Error calling tool:', error)\n const response: ToolCallResponse = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n res.status(500).json(response)\n }\n })\n }\n\n async initialize(): Promise<void> {\n await this.mcpServer.initialize()\n\n // Connect the MCP server to an in-memory transport\n // This is required for the MCP SDK to handle requests\n this.transport = new InMemoryTransport()\n const server = (this.mcpServer as any).server\n if (server) {\n await server.connect(this.transport)\n }\n\n this.isInitialized = true\n console.log('MCP Gateway initialized')\n }\n\n async start(): Promise<void> {\n await this.initialize()\n\n return new Promise(resolve => {\n this.httpServer = this.app.listen(this.config.port, this.config.host, () => {\n console.log(`MCP Gateway listening on http://${this.config.host}:${this.config.port}`)\n resolve()\n })\n })\n }\n\n async close(): Promise<void> {\n // Close HTTP server first\n if (this.httpServer) {\n await new Promise<void>((resolve, reject) => {\n this.httpServer!.close(err => {\n if (err) {\n console.error('Error closing HTTP server:', err)\n reject(err)\n } else {\n console.log('HTTP server closed')\n resolve()\n }\n })\n })\n }\n\n // Then close MCP server\n await this.mcpServer.close()\n console.log('MCP Gateway closed')\n }\n}\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport type { JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, JSONRPCErrorResponse } from '@modelcontextprotocol/sdk/types.js';\nimport { EventEmitter } from 'events';\n\n/**\n * In-Memory Transport for programmatic MCP server usage\n *\n * This transport creates a bidirectional message channel that allows\n * the MCP server to be used programmatically without stdio/SSE.\n */\nexport class InMemoryTransport implements Transport {\n sessionId?: string;\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n private started = false;\n private emitter = new EventEmitter();\n private responseHandlers = new Map<string | number, (response: JSONRPCMessage) => void>();\n\n async start(): Promise<void> {\n this.started = true;\n this.sessionId = `inmemory-${Date.now()}-${Math.random().toString(36).substring(7)}`;\n }\n\n async send(message: JSONRPCMessage): Promise<void> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n // If this is a response (has 'result' or 'error' and 'id')\n if ('result' in message || 'error' in message) {\n const response = message as JSONRPCResponse | JSONRPCErrorResponse;\n const handler = this.responseHandlers.get(response.id!);\n if (handler) {\n handler(message);\n this.responseHandlers.delete(response.id!);\n }\n }\n }\n\n async close(): Promise<void> {\n this.started = false;\n this.responseHandlers.clear();\n this.onclose?.();\n }\n\n setProtocolVersion?(version: string): void {\n // No-op for in-memory transport\n }\n\n /**\n * Send a request and wait for response\n * Used by the gateway to make programmatic requests\n */\n async sendRequest(request: JSONRPCMessage): Promise<JSONRPCMessage> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n return new Promise((resolve, reject) => {\n const req = request as JSONRPCRequest;\n const timeout = setTimeout(() => {\n this.responseHandlers.delete(req.id);\n reject(new Error('Request timeout'));\n }, 60000);\n\n this.responseHandlers.set(req.id, (response) => {\n clearTimeout(timeout);\n resolve(response);\n });\n\n // Route request to server's message handler\n if (this.onmessage) {\n this.onmessage(request);\n } else {\n reject(new Error('Transport not connected to server'));\n }\n });\n }\n}\n"],"mappings":"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","names":["express","cors"]}
|
package/dist/index.mjs
CHANGED
|
@@ -72,6 +72,36 @@ var MCPGatewayServer = class {
|
|
|
72
72
|
isInitialized = false;
|
|
73
73
|
httpServer;
|
|
74
74
|
transport;
|
|
75
|
+
/**
|
|
76
|
+
* Serialize {token-swap → sendRequest} sequences so concurrent dev
|
|
77
|
+
* requests can't race each other. Before Phase C this wrapper mutated
|
|
78
|
+
* `mcpServer.config.authToken` without any lock, so a slow request
|
|
79
|
+
* could pick up the token a faster concurrent request had just
|
|
80
|
+
* written — a classic confused-deputy inversion of privilege.
|
|
81
|
+
*
|
|
82
|
+
* The lock is chained: each caller awaits the previous tail, then
|
|
83
|
+
* claims the next tail. Errors don't poison the chain because we
|
|
84
|
+
* swallow them in the tail promise (callers still see their own
|
|
85
|
+
* error via the returned promise).
|
|
86
|
+
*/
|
|
87
|
+
requestChain = Promise.resolve();
|
|
88
|
+
async withTokenLock(token, op) {
|
|
89
|
+
const previous = this.requestChain;
|
|
90
|
+
let release;
|
|
91
|
+
this.requestChain = new Promise((resolve) => {
|
|
92
|
+
release = resolve;
|
|
93
|
+
});
|
|
94
|
+
try {
|
|
95
|
+
await previous.catch(() => void 0);
|
|
96
|
+
if (token !== void 0) {
|
|
97
|
+
;
|
|
98
|
+
this.mcpServer.config.authToken = token;
|
|
99
|
+
}
|
|
100
|
+
return await op();
|
|
101
|
+
} finally {
|
|
102
|
+
release();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
75
105
|
constructor(config) {
|
|
76
106
|
this.config = config;
|
|
77
107
|
this.app = express();
|
|
@@ -175,28 +205,19 @@ var MCPGatewayServer = class {
|
|
|
175
205
|
* This enables per-request auth so each API call can use its own token,
|
|
176
206
|
* rather than relying on the initial config token.
|
|
177
207
|
*/
|
|
178
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Extract a per-request bearer token from the Authorization header.
|
|
210
|
+
* Returns `undefined` on absent / malformed / unrecognised shapes so
|
|
211
|
+
* `withTokenLock` leaves the existing config token in place (rather
|
|
212
|
+
* than clobbering it with garbage).
|
|
213
|
+
*/
|
|
214
|
+
extractRequestToken(req) {
|
|
179
215
|
const authHeader = req.headers["authorization"];
|
|
180
|
-
if (!authHeader)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (!token) {
|
|
186
|
-
console.log("[MCP Gateway] Empty token after Bearer prefix removal");
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if (!token.startsWith("eyJ") && !token.startsWith("epic_")) {
|
|
190
|
-
console.log(`[MCP Gateway] Invalid token format (starts with: ${token.substring(0, 10)}...)`);
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
;
|
|
195
|
-
this.mcpServer.config.authToken = token;
|
|
196
|
-
console.log("[MCP Gateway] Auth token updated from request");
|
|
197
|
-
} catch (error) {
|
|
198
|
-
console.error("[MCP Gateway] Failed to update auth config:", error);
|
|
199
|
-
}
|
|
216
|
+
if (!authHeader) return void 0;
|
|
217
|
+
const token = authHeader.toString().replace("Bearer ", "").trim();
|
|
218
|
+
if (!token) return void 0;
|
|
219
|
+
if (!token.startsWith("eyJ") && !token.startsWith("epic_")) return void 0;
|
|
220
|
+
return token;
|
|
200
221
|
}
|
|
201
222
|
setupRoutes() {
|
|
202
223
|
this.app.get("/health", (req, res) => {
|
|
@@ -213,8 +234,10 @@ var MCPGatewayServer = class {
|
|
|
213
234
|
error: "Server not initialized"
|
|
214
235
|
});
|
|
215
236
|
}
|
|
216
|
-
this.
|
|
217
|
-
|
|
237
|
+
const tools = await this.withTokenLock(
|
|
238
|
+
this.extractRequestToken(req),
|
|
239
|
+
() => this.listTools()
|
|
240
|
+
);
|
|
218
241
|
res.json({ tools });
|
|
219
242
|
} catch (error) {
|
|
220
243
|
console.error("Error listing tools:", error);
|
|
@@ -231,7 +254,6 @@ var MCPGatewayServer = class {
|
|
|
231
254
|
error: "Server not initialized"
|
|
232
255
|
});
|
|
233
256
|
}
|
|
234
|
-
this.updateAuthFromRequest(req);
|
|
235
257
|
const { toolName, arguments: args } = req.body;
|
|
236
258
|
if (!toolName) {
|
|
237
259
|
return res.status(400).json({
|
|
@@ -239,7 +261,10 @@ var MCPGatewayServer = class {
|
|
|
239
261
|
error: "toolName is required"
|
|
240
262
|
});
|
|
241
263
|
}
|
|
242
|
-
const result = await this.
|
|
264
|
+
const result = await this.withTokenLock(
|
|
265
|
+
this.extractRequestToken(req),
|
|
266
|
+
() => this.callTool(toolName, args || {})
|
|
267
|
+
);
|
|
243
268
|
const response = {
|
|
244
269
|
success: true,
|
|
245
270
|
result
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/InMemoryTransport.ts"],"sourcesContent":["// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport express, { Express, Request, Response } from 'express'\nimport cors from 'cors'\nimport { FlowStateMCPServer } from '@epicdm/flowstate-mcp'\nimport { InMemoryTransport } from './InMemoryTransport'\nimport type { MCPGatewayConfig, ToolCallRequest, ToolCallResponse } from './types'\n\nexport class MCPGatewayServer {\n private app: Express\n private mcpServer: FlowStateMCPServer\n private config: MCPGatewayConfig\n private isInitialized = false\n private httpServer?: ReturnType<Express['listen']>\n private transport?: InMemoryTransport\n\n constructor(config: MCPGatewayConfig) {\n this.config = config\n this.app = express()\n\n // Initialize MCP Server\n this.mcpServer = new FlowStateMCPServer({\n rxdbServerUrl: config.rxdbServerUrl,\n domainId: config.domainId,\n projectPath: config.projectPath || process.cwd(),\n ...(config.userId && { userId: config.userId }),\n ...(config.orgId && { orgId: config.orgId }),\n } as any)\n\n this.setupMiddleware()\n this.setupRoutes()\n }\n\n /**\n * Get list of tools from MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for listing tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n */\n private async listTools(): Promise<any[]> {\n if (!this.transport) {\n return []\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/list' as const,\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to list tools')\n }\n\n return response.result?.tools || []\n }\n\n /**\n * Call a tool on the MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for calling tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n *\n * NOTE: The MCP SDK's server.request() method appears to require the request\n * object to be passed twice (once as the request, once as params). This seems\n * to be how the underlying MCP SDK expects the call to be structured based on\n * the protocol specification.\n */\n private async callTool(toolName: string, args: Record<string, any>): Promise<any> {\n if (!this.transport) {\n throw new Error('MCP server not available')\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/call' as const,\n params: {\n name: toolName,\n arguments: args,\n },\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to call tool')\n }\n\n return response.result?.content?.[0]?.text || response.result\n }\n\n private setupMiddleware(): void {\n // CORS\n this.app.use(\n cors({\n origin: this.config.corsOrigins || '*',\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n })\n )\n\n // Body parsing\n this.app.use(express.json())\n this.app.use(express.urlencoded({ extended: true }))\n\n // Request logging\n this.app.use((req, res, next) => {\n console.log(`${new Date().toISOString()} ${req.method} ${req.path}`)\n next()\n })\n }\n\n /**\n * Extract and forward auth token from request to MCP server\n *\n * This enables per-request auth so each API call can use its own token,\n * rather than relying on the initial config token.\n */\n private updateAuthFromRequest(req: Request): void {\n const authHeader = req.headers['authorization']\n if (!authHeader) {\n console.log('[MCP Gateway] No Authorization header in request')\n return\n }\n\n const token = authHeader.toString().replace('Bearer ', '')\n if (!token) {\n console.log('[MCP Gateway] Empty token after Bearer prefix removal')\n return\n }\n\n // Only accept valid-looking tokens (JWT or API tokens)\n if (!token.startsWith('eyJ') && !token.startsWith('epic_')) {\n console.log(`[MCP Gateway] Invalid token format (starts with: ${token.substring(0, 10)}...)`)\n return\n }\n\n try {\n ;(this.mcpServer as any).config.authToken = token\n console.log('[MCP Gateway] Auth token updated from request')\n } catch (error) {\n console.error('[MCP Gateway] Failed to update auth config:', error)\n }\n }\n\n private setupRoutes(): void {\n // Health check\n this.app.get('/health', (req: Request, res: Response) => {\n res.json({\n status: 'ok',\n initialized: this.isInitialized,\n timestamp: new Date().toISOString(),\n })\n })\n\n // List available tools\n this.app.get('/mcp/tools', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n error: 'Server not initialized',\n })\n }\n\n // Forward per-request auth to MCP server\n this.updateAuthFromRequest(req)\n\n const tools = await this.listTools()\n res.json({ tools })\n } catch (error) {\n console.error('Error listing tools:', error)\n res.status(500).json({\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })\n\n // Execute a tool\n this.app.post('/mcp/tools/call', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n success: false,\n error: 'Server not initialized',\n })\n }\n\n // Forward per-request auth to MCP server\n this.updateAuthFromRequest(req)\n\n const { toolName, arguments: args } = req.body as ToolCallRequest\n\n if (!toolName) {\n return res.status(400).json({\n success: false,\n error: 'toolName is required',\n })\n }\n\n const result = await this.callTool(toolName, args || {})\n\n const response: ToolCallResponse = {\n success: true,\n result,\n }\n\n res.json(response)\n } catch (error) {\n console.error('Error calling tool:', error)\n const response: ToolCallResponse = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n res.status(500).json(response)\n }\n })\n }\n\n async initialize(): Promise<void> {\n await this.mcpServer.initialize()\n\n // Connect the MCP server to an in-memory transport\n // This is required for the MCP SDK to handle requests\n this.transport = new InMemoryTransport()\n const server = (this.mcpServer as any).server\n if (server) {\n await server.connect(this.transport)\n }\n\n this.isInitialized = true\n console.log('MCP Gateway initialized')\n }\n\n async start(): Promise<void> {\n await this.initialize()\n\n return new Promise(resolve => {\n this.httpServer = this.app.listen(this.config.port, this.config.host, () => {\n console.log(`MCP Gateway listening on http://${this.config.host}:${this.config.port}`)\n resolve()\n })\n })\n }\n\n async close(): Promise<void> {\n // Close HTTP server first\n if (this.httpServer) {\n await new Promise<void>((resolve, reject) => {\n this.httpServer!.close(err => {\n if (err) {\n console.error('Error closing HTTP server:', err)\n reject(err)\n } else {\n console.log('HTTP server closed')\n resolve()\n }\n })\n })\n }\n\n // Then close MCP server\n await this.mcpServer.close()\n console.log('MCP Gateway closed')\n }\n}\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport type { JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, JSONRPCErrorResponse } from '@modelcontextprotocol/sdk/types.js';\nimport { EventEmitter } from 'events';\n\n/**\n * In-Memory Transport for programmatic MCP server usage\n *\n * This transport creates a bidirectional message channel that allows\n * the MCP server to be used programmatically without stdio/SSE.\n */\nexport class InMemoryTransport implements Transport {\n sessionId?: string;\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n private started = false;\n private emitter = new EventEmitter();\n private responseHandlers = new Map<string | number, (response: JSONRPCMessage) => void>();\n\n async start(): Promise<void> {\n this.started = true;\n this.sessionId = `inmemory-${Date.now()}-${Math.random().toString(36).substring(7)}`;\n }\n\n async send(message: JSONRPCMessage): Promise<void> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n // If this is a response (has 'result' or 'error' and 'id')\n if ('result' in message || 'error' in message) {\n const response = message as JSONRPCResponse | JSONRPCErrorResponse;\n const handler = this.responseHandlers.get(response.id!);\n if (handler) {\n handler(message);\n this.responseHandlers.delete(response.id!);\n }\n }\n }\n\n async close(): Promise<void> {\n this.started = false;\n this.responseHandlers.clear();\n this.onclose?.();\n }\n\n setProtocolVersion?(version: string): void {\n // No-op for in-memory transport\n }\n\n /**\n * Send a request and wait for response\n * Used by the gateway to make programmatic requests\n */\n async sendRequest(request: JSONRPCMessage): Promise<JSONRPCMessage> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n return new Promise((resolve, reject) => {\n const req = request as JSONRPCRequest;\n const timeout = setTimeout(() => {\n this.responseHandlers.delete(req.id);\n reject(new Error('Request timeout'));\n }, 60000);\n\n this.responseHandlers.set(req.id, (response) => {\n clearTimeout(timeout);\n resolve(response);\n });\n\n // Route request to server's message handler\n if (this.onmessage) {\n this.onmessage(request);\n } else {\n reject(new Error('Transport not connected to server'));\n }\n });\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/InMemoryTransport.ts"],"sourcesContent":["// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport express, { Express, Request, Response } from 'express'\nimport cors from 'cors'\nimport { FlowStateMCPServer } from '@epicdm/flowstate-mcp'\nimport { InMemoryTransport } from './InMemoryTransport'\nimport type { MCPGatewayConfig, ToolCallRequest, ToolCallResponse } from './types'\n\nexport class MCPGatewayServer {\n private app: Express\n private mcpServer: FlowStateMCPServer\n private config: MCPGatewayConfig\n private isInitialized = false\n private httpServer?: ReturnType<Express['listen']>\n private transport?: InMemoryTransport\n\n /**\n * Serialize {token-swap → sendRequest} sequences so concurrent dev\n * requests can't race each other. Before Phase C this wrapper mutated\n * `mcpServer.config.authToken` without any lock, so a slow request\n * could pick up the token a faster concurrent request had just\n * written — a classic confused-deputy inversion of privilege.\n *\n * The lock is chained: each caller awaits the previous tail, then\n * claims the next tail. Errors don't poison the chain because we\n * swallow them in the tail promise (callers still see their own\n * error via the returned promise).\n */\n private requestChain: Promise<unknown> = Promise.resolve()\n private async withTokenLock<T>(token: string | undefined, op: () => Promise<T>): Promise<T> {\n const previous = this.requestChain\n let release!: () => void\n this.requestChain = new Promise<void>((resolve) => {\n release = resolve\n })\n try {\n await previous.catch(() => undefined)\n if (token !== undefined) {\n ;(this.mcpServer as unknown as { config: { authToken?: string } }).config.authToken = token\n }\n return await op()\n } finally {\n release()\n }\n }\n\n constructor(config: MCPGatewayConfig) {\n this.config = config\n this.app = express()\n\n // Initialize MCP Server\n this.mcpServer = new FlowStateMCPServer({\n rxdbServerUrl: config.rxdbServerUrl,\n domainId: config.domainId,\n projectPath: config.projectPath || process.cwd(),\n ...(config.userId && { userId: config.userId }),\n ...(config.orgId && { orgId: config.orgId }),\n } as any)\n\n this.setupMiddleware()\n this.setupRoutes()\n }\n\n /**\n * Get list of tools from MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for listing tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n */\n private async listTools(): Promise<any[]> {\n if (!this.transport) {\n return []\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/list' as const,\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to list tools')\n }\n\n return response.result?.tools || []\n }\n\n /**\n * Call a tool on the MCP server\n *\n * WARNING: This method accesses internal MCP server API via type casting.\n * This is necessary because FlowStateMCPServer doesn't expose a public method\n * for calling tools. The underlying server.request() method is used directly.\n *\n * RISKS:\n * - Internal API may change without notice in future versions\n * - Type casting bypasses TypeScript safety checks\n * - May break if FlowStateMCPServer implementation changes\n *\n * TODO: Consider requesting a public API method in FlowStateMCPServer\n * for listing and calling tools to avoid internal API access.\n *\n * NOTE: The MCP SDK's server.request() method appears to require the request\n * object to be passed twice (once as the request, once as params). This seems\n * to be how the underlying MCP SDK expects the call to be structured based on\n * the protocol specification.\n */\n private async callTool(toolName: string, args: Record<string, any>): Promise<any> {\n if (!this.transport) {\n throw new Error('MCP server not available')\n }\n\n // Send request through transport\n const request = {\n jsonrpc: '2.0' as const,\n id: Date.now(),\n method: 'tools/call' as const,\n params: {\n name: toolName,\n arguments: args,\n },\n }\n\n const response: any = await this.transport.sendRequest(request)\n\n if ('error' in response) {\n throw new Error(response.error.message || 'Failed to call tool')\n }\n\n return response.result?.content?.[0]?.text || response.result\n }\n\n private setupMiddleware(): void {\n // CORS\n this.app.use(\n cors({\n origin: this.config.corsOrigins || '*',\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n })\n )\n\n // Body parsing\n this.app.use(express.json())\n this.app.use(express.urlencoded({ extended: true }))\n\n // Request logging\n this.app.use((req, res, next) => {\n console.log(`${new Date().toISOString()} ${req.method} ${req.path}`)\n next()\n })\n }\n\n /**\n * Extract and forward auth token from request to MCP server\n *\n * This enables per-request auth so each API call can use its own token,\n * rather than relying on the initial config token.\n */\n /**\n * Extract a per-request bearer token from the Authorization header.\n * Returns `undefined` on absent / malformed / unrecognised shapes so\n * `withTokenLock` leaves the existing config token in place (rather\n * than clobbering it with garbage).\n */\n private extractRequestToken(req: Request): string | undefined {\n const authHeader = req.headers['authorization']\n if (!authHeader) return undefined\n const token = authHeader.toString().replace('Bearer ', '').trim()\n if (!token) return undefined\n if (!token.startsWith('eyJ') && !token.startsWith('epic_')) return undefined\n return token\n }\n\n private setupRoutes(): void {\n // Health check\n this.app.get('/health', (req: Request, res: Response) => {\n res.json({\n status: 'ok',\n initialized: this.isInitialized,\n timestamp: new Date().toISOString(),\n })\n })\n\n // List available tools\n this.app.get('/mcp/tools', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n error: 'Server not initialized',\n })\n }\n\n // Phase C: swap token + issue request atomically under the\n // per-request chain so concurrent requests can't inversion of\n // privilege each other.\n const tools = await this.withTokenLock(this.extractRequestToken(req), () =>\n this.listTools()\n )\n res.json({ tools })\n } catch (error) {\n console.error('Error listing tools:', error)\n res.status(500).json({\n error: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n })\n\n // Execute a tool\n this.app.post('/mcp/tools/call', async (req: Request, res: Response) => {\n try {\n if (!this.isInitialized) {\n return res.status(503).json({\n success: false,\n error: 'Server not initialized',\n })\n }\n\n const { toolName, arguments: args } = req.body as ToolCallRequest\n\n if (!toolName) {\n return res.status(400).json({\n success: false,\n error: 'toolName is required',\n })\n }\n\n const result = await this.withTokenLock(this.extractRequestToken(req), () =>\n this.callTool(toolName, args || {})\n )\n\n const response: ToolCallResponse = {\n success: true,\n result,\n }\n\n res.json(response)\n } catch (error) {\n console.error('Error calling tool:', error)\n const response: ToolCallResponse = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n res.status(500).json(response)\n }\n })\n }\n\n async initialize(): Promise<void> {\n await this.mcpServer.initialize()\n\n // Connect the MCP server to an in-memory transport\n // This is required for the MCP SDK to handle requests\n this.transport = new InMemoryTransport()\n const server = (this.mcpServer as any).server\n if (server) {\n await server.connect(this.transport)\n }\n\n this.isInitialized = true\n console.log('MCP Gateway initialized')\n }\n\n async start(): Promise<void> {\n await this.initialize()\n\n return new Promise(resolve => {\n this.httpServer = this.app.listen(this.config.port, this.config.host, () => {\n console.log(`MCP Gateway listening on http://${this.config.host}:${this.config.port}`)\n resolve()\n })\n })\n }\n\n async close(): Promise<void> {\n // Close HTTP server first\n if (this.httpServer) {\n await new Promise<void>((resolve, reject) => {\n this.httpServer!.close(err => {\n if (err) {\n console.error('Error closing HTTP server:', err)\n reject(err)\n } else {\n console.log('HTTP server closed')\n resolve()\n }\n })\n })\n }\n\n // Then close MCP server\n await this.mcpServer.close()\n console.log('MCP Gateway closed')\n }\n}\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport type { JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, JSONRPCErrorResponse } from '@modelcontextprotocol/sdk/types.js';\nimport { EventEmitter } from 'events';\n\n/**\n * In-Memory Transport for programmatic MCP server usage\n *\n * This transport creates a bidirectional message channel that allows\n * the MCP server to be used programmatically without stdio/SSE.\n */\nexport class InMemoryTransport implements Transport {\n sessionId?: string;\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n private started = false;\n private emitter = new EventEmitter();\n private responseHandlers = new Map<string | number, (response: JSONRPCMessage) => void>();\n\n async start(): Promise<void> {\n this.started = true;\n this.sessionId = `inmemory-${Date.now()}-${Math.random().toString(36).substring(7)}`;\n }\n\n async send(message: JSONRPCMessage): Promise<void> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n // If this is a response (has 'result' or 'error' and 'id')\n if ('result' in message || 'error' in message) {\n const response = message as JSONRPCResponse | JSONRPCErrorResponse;\n const handler = this.responseHandlers.get(response.id!);\n if (handler) {\n handler(message);\n this.responseHandlers.delete(response.id!);\n }\n }\n }\n\n async close(): Promise<void> {\n this.started = false;\n this.responseHandlers.clear();\n this.onclose?.();\n }\n\n setProtocolVersion?(version: string): void {\n // No-op for in-memory transport\n }\n\n /**\n * Send a request and wait for response\n * Used by the gateway to make programmatic requests\n */\n async sendRequest(request: JSONRPCMessage): Promise<JSONRPCMessage> {\n if (!this.started) {\n throw new Error('Transport not started');\n }\n\n return new Promise((resolve, reject) => {\n const req = request as JSONRPCRequest;\n const timeout = setTimeout(() => {\n this.responseHandlers.delete(req.id);\n reject(new Error('Request timeout'));\n }, 60000);\n\n this.responseHandlers.set(req.id, (response) => {\n clearTimeout(timeout);\n resolve(response);\n });\n\n // Route request to server's message handler\n if (this.onmessage) {\n this.onmessage(request);\n } else {\n reject(new Error('Transport not connected to server'));\n }\n });\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epicdm/flowstate-mcp-gateway",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "HTTP/REST gateway for FlowState MCP server",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"lint": "eslint src --ext .ts"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@epicdm/flowstate-mcp": "
|
|
28
|
+
"@epicdm/flowstate-mcp": "1.1.0",
|
|
29
29
|
"cors": "^2.8.5",
|
|
30
30
|
"dotenv": "^16.4.5",
|
|
31
31
|
"express": "^4.18.2"
|