@mcpjam/inspector 0.3.9 โ 0.8.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/.next/BUILD_ID +1 -0
- package/.next/app-build-manifest.json +89 -0
- package/.next/app-path-routes-manifest.json +13 -0
- package/.next/build-manifest.json +33 -0
- package/.next/cache/.previewinfo +1 -0
- package/.next/cache/.rscinfo +1 -0
- package/.next/cache/.tsbuildinfo +1 -0
- package/.next/cache/eslint/.cache_11b5ofe +1 -0
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/diagnostics/build-diagnostics.json +6 -0
- package/.next/diagnostics/framework.json +1 -0
- package/.next/export-marker.json +6 -0
- package/.next/images-manifest.json +57 -0
- package/.next/next-minimal-server.js.nft.json +1 -0
- package/.next/next-server.js.nft.json +1 -0
- package/.next/package.json +1 -0
- package/.next/prerender-manifest.json +41 -0
- package/.next/react-loadable-manifest.json +1 -0
- package/.next/required-server-files.json +318 -0
- package/.next/routes-manifest.json +65 -0
- package/.next/server/app/_not-found/page.js +2 -0
- package/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
- package/.next/server/app/api/mcp/chat/route.js +45 -0
- package/.next/server/app/api/mcp/chat/route.js.nft.json +1 -0
- package/.next/server/app/api/mcp/chat/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/mcp/connect/route.js +1 -0
- package/.next/server/app/api/mcp/connect/route.js.nft.json +1 -0
- package/.next/server/app/api/mcp/connect/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/mcp/prompts/get/route.js +1 -0
- package/.next/server/app/api/mcp/prompts/get/route.js.nft.json +1 -0
- package/.next/server/app/api/mcp/prompts/get/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/mcp/prompts/list/route.js +1 -0
- package/.next/server/app/api/mcp/prompts/list/route.js.nft.json +1 -0
- package/.next/server/app/api/mcp/prompts/list/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/mcp/resources/list/route.js +1 -0
- package/.next/server/app/api/mcp/resources/list/route.js.nft.json +1 -0
- package/.next/server/app/api/mcp/resources/list/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/mcp/resources/read/route.js +1 -0
- package/.next/server/app/api/mcp/resources/read/route.js.nft.json +1 -0
- package/.next/server/app/api/mcp/resources/read/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/mcp/tools/route.js +21 -0
- package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -0
- package/.next/server/app/api/mcp/tools/route_client-reference-manifest.js +1 -0
- package/.next/server/app/favicon.ico/route.js +1 -0
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -0
- package/.next/server/app/favicon.ico.body +0 -0
- package/.next/server/app/favicon.ico.meta +1 -0
- package/.next/server/app/oauth/callback/page.js +2 -0
- package/.next/server/app/oauth/callback/page.js.nft.json +1 -0
- package/.next/server/app/oauth/callback/page_client-reference-manifest.js +1 -0
- package/.next/server/app/page.js +16 -0
- package/.next/server/app/page.js.nft.json +1 -0
- package/.next/server/app/page_client-reference-manifest.js +1 -0
- package/.next/server/app-paths-manifest.json +13 -0
- package/.next/server/chunks/175.js +8 -0
- package/.next/server/chunks/260.js +82 -0
- package/.next/server/chunks/546.js +1 -0
- package/.next/server/chunks/548.js +6 -0
- package/.next/server/chunks/55.js +1 -0
- package/.next/server/chunks/985.js +22 -0
- package/.next/server/functions-config-manifest.json +4 -0
- package/.next/server/interception-route-rewrite-manifest.js +1 -0
- package/.next/server/middleware-build-manifest.js +1 -0
- package/.next/server/middleware-manifest.json +6 -0
- package/.next/server/middleware-react-loadable-manifest.js +1 -0
- package/.next/server/next-font-manifest.js +1 -0
- package/.next/server/next-font-manifest.json +1 -0
- package/.next/server/pages/500.html +1 -0
- package/.next/server/pages/_app.js +1 -0
- package/.next/server/pages/_app.js.nft.json +1 -0
- package/.next/server/pages/_document.js +1 -0
- package/.next/server/pages/_document.js.nft.json +1 -0
- package/.next/server/pages/_error.js +19 -0
- package/.next/server/pages/_error.js.nft.json +1 -0
- package/.next/server/pages-manifest.json +5 -0
- package/.next/server/server-reference-manifest.js +1 -0
- package/.next/server/server-reference-manifest.json +1 -0
- package/.next/server/webpack-runtime.js +1 -0
- package/.next/static/chunks/14-ae3a01e72ea53777.js +1 -0
- package/.next/static/chunks/214-cc4c35d88f2695ed.js +1 -0
- package/.next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js +1 -0
- package/.next/static/chunks/866-04c19dda4c52f2bf.js +1 -0
- package/.next/static/chunks/964-eda38e26c0391a47.js +1 -0
- package/.next/static/chunks/app/_not-found/page-d7e832b54474da82.js +1 -0
- package/.next/static/chunks/app/api/mcp/chat/route-0341498a8bf5f2da.js +1 -0
- package/.next/static/chunks/app/api/mcp/connect/route-0341498a8bf5f2da.js +1 -0
- package/.next/static/chunks/app/api/mcp/prompts/get/route-0341498a8bf5f2da.js +1 -0
- package/.next/static/chunks/app/api/mcp/prompts/list/route-0341498a8bf5f2da.js +1 -0
- package/.next/static/chunks/app/api/mcp/resources/list/route-0341498a8bf5f2da.js +1 -0
- package/.next/static/chunks/app/api/mcp/resources/read/route-0341498a8bf5f2da.js +1 -0
- package/.next/static/chunks/app/api/mcp/tools/route-0341498a8bf5f2da.js +1 -0
- package/.next/static/chunks/app/layout-fb6e1ad5933381f3.js +1 -0
- package/.next/static/chunks/app/oauth/callback/page-d8b3908ea67ba3e3.js +1 -0
- package/.next/static/chunks/app/page-81e35b2a61edb363.js +1 -0
- package/.next/static/chunks/framework-7c95b8e5103c9e90.js +1 -0
- package/.next/static/chunks/main-app-7d61da15faa6c1af.js +1 -0
- package/.next/static/chunks/main-bbdafee21a7bd1d6.js +1 -0
- package/.next/static/chunks/pages/_app-0a0020ddd67f79cf.js +1 -0
- package/.next/static/chunks/pages/_error-03529f2c21436739.js +1 -0
- package/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/.next/static/chunks/webpack-cdfccaf38062dd25.js +1 -0
- package/.next/static/css/1e852d83e9c1d0c6.css +1 -0
- package/.next/static/css/f30152c0704fba31.css +1 -0
- package/.next/static/css/fe751fdbe975e9ca.css +1 -0
- package/.next/static/media/569ce4b8f30dc480-s.p.woff2 +0 -0
- package/.next/static/media/747892c23ea88013-s.woff2 +0 -0
- package/.next/static/media/8d697b304b401681-s.woff2 +0 -0
- package/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
- package/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
- package/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
- package/.next/static/media/ollama_dark.9af45ac0.png +0 -0
- package/.next/static/media/ollama_logo.9f08a95b.svg +7 -0
- package/.next/static/media/openai_logo.3f83154a.png +0 -0
- package/.next/static/wgHmsxKAquUu9gOMW6Qd5/_buildManifest.js +1 -0
- package/.next/static/wgHmsxKAquUu9gOMW6Qd5/_ssgManifest.js +1 -0
- package/.next/trace +35 -0
- package/.next/types/app/api/mcp/chat/route.ts +347 -0
- package/.next/types/app/api/mcp/connect/route.ts +347 -0
- package/.next/types/app/api/mcp/prompts/get/route.ts +347 -0
- package/.next/types/app/api/mcp/prompts/list/route.ts +347 -0
- package/.next/types/app/api/mcp/resources/list/route.ts +347 -0
- package/.next/types/app/api/mcp/resources/read/route.ts +347 -0
- package/.next/types/app/api/mcp/tools/route.ts +347 -0
- package/.next/types/app/layout.ts +84 -0
- package/.next/types/app/oauth/callback/page.ts +84 -0
- package/.next/types/app/page.ts +84 -0
- package/.next/types/cache-life.d.ts +141 -0
- package/.next/types/package.json +1 -0
- package/README.md +76 -161
- package/bin/start.js +504 -0
- package/next.config.ts +7 -0
- package/package.json +71 -54
- package/public/claude_logo.png +0 -0
- package/public/demo_1.png +0 -0
- package/public/demo_2.png +0 -0
- package/public/demo_3.png +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/mcp.svg +1 -0
- package/public/next.svg +1 -0
- package/public/ollama_dark.png +0 -0
- package/public/ollama_logo.svg +7 -0
- package/public/openai_logo.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/LICENSE +0 -200
- package/cli/build/cli.js +0 -251
- package/cli/build/client/connection.js +0 -33
- package/cli/build/client/index.js +0 -6
- package/cli/build/client/prompts.js +0 -23
- package/cli/build/client/resources.js +0 -30
- package/cli/build/client/tools.js +0 -64
- package/cli/build/client/types.js +0 -1
- package/cli/build/error-handler.js +0 -18
- package/cli/build/index.js +0 -166
- package/cli/build/transport.js +0 -47
- package/client/bin/client.js +0 -71
- package/client/bin/start.js +0 -143
- package/client/dist/assets/OAuthCallback-BSOXmPlE.js +0 -56
- package/client/dist/assets/OAuthDebugCallback-DyzqkofK.js +0 -44
- package/client/dist/assets/index-BT03cD-1.js +0 -63301
- package/client/dist/assets/index-Bwd_BFIj.css +0 -4164
- package/client/dist/index.html +0 -14
- package/client/dist/ollama_logo.png +0 -0
- package/client/dist/openai_logo.png +0 -0
- package/server/build/database/DatabaseManager.js +0 -108
- package/server/build/database/index.js +0 -8
- package/server/build/database/routes.js +0 -86
- package/server/build/database/types.js +0 -27
- package/server/build/database/utils.js +0 -86
- package/server/build/index.js +0 -331
- package/server/build/mcpProxy.js +0 -54
- package/server/build/shared/MCPProxyService.js +0 -221
- package/server/build/shared/TransportFactory.js +0 -130
- package/server/build/shared/index.js +0 -4
- package/server/build/shared/types.js +0 -1
- package/server/build/shared/utils.js +0 -27
- package/server/build/test-server.js +0 -145
- package/server/build/testing/HealthCheck.js +0 -42
- package/server/build/testing/TestExecutor.js +0 -240
- package/server/build/testing/TestRunner.js +0 -198
- package/server/build/testing/TestServer.js +0 -440
- package/server/build/testing/types.js +0 -1
- /package/{client/dist/claude_logo.png โ .next/static/media/claude_logo.d33b25b0.png} +0 -0
- /package/{client/dist โ public}/mcp_jam.svg +0 -0
- /package/{client/dist โ public}/mcp_jam_dark.png +0 -0
- /package/{client/dist โ public}/mcp_jam_light.png +0 -0
package/server/build/mcpProxy.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { isJSONRPCRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
-
function onClientError(error) {
|
|
3
|
-
console.error("Error from inspector client:", error);
|
|
4
|
-
}
|
|
5
|
-
function onServerError(error) {
|
|
6
|
-
if ((error?.message &&
|
|
7
|
-
error.message.includes("Error POSTing to endpoint (HTTP 404)")) ||
|
|
8
|
-
(error?.cause && JSON.stringify(error.cause).includes("ECONNREFUSED"))) {
|
|
9
|
-
console.error("Connection refused. Is the MCP server running?");
|
|
10
|
-
}
|
|
11
|
-
else {
|
|
12
|
-
console.error("Error from MCP server:", error);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
export default function mcpProxy({ transportToClient, transportToServer, }) {
|
|
16
|
-
let transportToClientClosed = false;
|
|
17
|
-
let transportToServerClosed = false;
|
|
18
|
-
transportToClient.onmessage = (message) => {
|
|
19
|
-
transportToServer.send(message).catch((error) => {
|
|
20
|
-
// Send error response back to client if it was a request (has id) and connection is still open
|
|
21
|
-
if (isJSONRPCRequest(message) && !transportToClientClosed) {
|
|
22
|
-
const errorResponse = {
|
|
23
|
-
jsonrpc: "2.0",
|
|
24
|
-
id: message.id,
|
|
25
|
-
error: {
|
|
26
|
-
code: -32001,
|
|
27
|
-
message: error.message,
|
|
28
|
-
data: error,
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
transportToClient.send(errorResponse).catch(onClientError);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
};
|
|
35
|
-
transportToServer.onmessage = (message) => {
|
|
36
|
-
transportToClient.send(message).catch(onClientError);
|
|
37
|
-
};
|
|
38
|
-
transportToClient.onclose = () => {
|
|
39
|
-
if (transportToServerClosed) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
transportToClientClosed = true;
|
|
43
|
-
transportToServer.close().catch(onServerError);
|
|
44
|
-
};
|
|
45
|
-
transportToServer.onclose = () => {
|
|
46
|
-
if (transportToClientClosed) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
transportToServerClosed = true;
|
|
50
|
-
transportToClient.close().catch(onClientError);
|
|
51
|
-
};
|
|
52
|
-
transportToClient.onerror = onClientError;
|
|
53
|
-
transportToServer.onerror = onServerError;
|
|
54
|
-
}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from "events";
|
|
2
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
|
-
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
4
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
5
|
-
import { TransportFactory } from "./TransportFactory.js";
|
|
6
|
-
import { generateSessionId, ConsoleLogger } from "./utils.js";
|
|
7
|
-
import mcpProxy from "../mcpProxy.js";
|
|
8
|
-
export class MCPProxyService extends EventEmitter {
|
|
9
|
-
webAppTransports = new Map();
|
|
10
|
-
backingServerTransports = new Map();
|
|
11
|
-
connectionStatus = new Map();
|
|
12
|
-
cleanupInProgress = new Set();
|
|
13
|
-
transportFactory;
|
|
14
|
-
logger;
|
|
15
|
-
maxConnections;
|
|
16
|
-
constructor(options = {}) {
|
|
17
|
-
super();
|
|
18
|
-
this.logger = options.logger || new ConsoleLogger();
|
|
19
|
-
this.maxConnections = options.maxConnections || 50;
|
|
20
|
-
this.transportFactory = new TransportFactory({
|
|
21
|
-
logger: this.logger,
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
async createConnection(serverConfig, requestHeaders) {
|
|
25
|
-
if (this.backingServerTransports.size >= this.maxConnections) {
|
|
26
|
-
throw new Error(`Maximum connections reached (${this.maxConnections})`);
|
|
27
|
-
}
|
|
28
|
-
const sessionId = generateSessionId();
|
|
29
|
-
try {
|
|
30
|
-
this.logger.info(`Creating connection ${sessionId} for ${serverConfig.name}`);
|
|
31
|
-
// Update status to connecting
|
|
32
|
-
this.connectionStatus.set(sessionId, {
|
|
33
|
-
id: sessionId,
|
|
34
|
-
status: "connecting",
|
|
35
|
-
lastActivity: new Date(),
|
|
36
|
-
errorCount: 0,
|
|
37
|
-
});
|
|
38
|
-
// Create transport
|
|
39
|
-
const transport = await this.transportFactory.createTransport(serverConfig, requestHeaders);
|
|
40
|
-
// Store transport
|
|
41
|
-
this.backingServerTransports.set(sessionId, transport);
|
|
42
|
-
// Set up transport event handlers
|
|
43
|
-
this.setupTransportEvents(sessionId, transport);
|
|
44
|
-
// Update status to connected
|
|
45
|
-
this.updateConnectionStatus(sessionId, "connected");
|
|
46
|
-
this.emit("connection", sessionId, serverConfig);
|
|
47
|
-
return sessionId;
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
this.updateConnectionStatus(sessionId, "error");
|
|
51
|
-
this.logger.error(`Failed to create connection ${sessionId}:`, error);
|
|
52
|
-
throw error;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
getActiveConnections() {
|
|
56
|
-
return Array.from(this.backingServerTransports.keys());
|
|
57
|
-
}
|
|
58
|
-
getConnectionStatus(sessionId) {
|
|
59
|
-
return this.connectionStatus.get(sessionId);
|
|
60
|
-
}
|
|
61
|
-
getAllConnectionStatuses() {
|
|
62
|
-
return Array.from(this.connectionStatus.values());
|
|
63
|
-
}
|
|
64
|
-
async sendMessage(sessionId, message) {
|
|
65
|
-
const transport = this.backingServerTransports.get(sessionId);
|
|
66
|
-
if (!transport) {
|
|
67
|
-
throw new Error(`No transport found for session: ${sessionId}`);
|
|
68
|
-
}
|
|
69
|
-
try {
|
|
70
|
-
this.updateConnectionStatus(sessionId, "connected");
|
|
71
|
-
await transport.send(message);
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
this.incrementErrorCount(sessionId);
|
|
75
|
-
this.logger.error(`Message failed for session ${sessionId}:`, error);
|
|
76
|
-
throw error;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
getTransport(sessionId) {
|
|
80
|
-
return this.backingServerTransports.get(sessionId);
|
|
81
|
-
}
|
|
82
|
-
getWebAppTransport(sessionId) {
|
|
83
|
-
return this.webAppTransports.get(sessionId);
|
|
84
|
-
}
|
|
85
|
-
setWebAppTransport(sessionId, transport) {
|
|
86
|
-
this.webAppTransports.set(sessionId, transport);
|
|
87
|
-
this.logger.info(`Web app transport set for session ${sessionId}`);
|
|
88
|
-
}
|
|
89
|
-
removeWebAppTransport(sessionId) {
|
|
90
|
-
this.webAppTransports.delete(sessionId);
|
|
91
|
-
this.logger.info(`Web app transport removed for session ${sessionId}`);
|
|
92
|
-
}
|
|
93
|
-
async closeConnection(sessionId) {
|
|
94
|
-
// Prevent duplicate cleanup calls
|
|
95
|
-
if (this.cleanupInProgress.has(sessionId)) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
this.cleanupInProgress.add(sessionId);
|
|
99
|
-
try {
|
|
100
|
-
const transport = this.backingServerTransports.get(sessionId);
|
|
101
|
-
if (transport) {
|
|
102
|
-
try {
|
|
103
|
-
await transport.close();
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
this.logger.error(`Error closing connection ${sessionId}:`, error);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
this.backingServerTransports.delete(sessionId);
|
|
110
|
-
this.webAppTransports.delete(sessionId);
|
|
111
|
-
this.connectionStatus.delete(sessionId);
|
|
112
|
-
this.emit("disconnection", sessionId);
|
|
113
|
-
this.logger.info(`๐งน Cleaning up transports for session ${sessionId}`);
|
|
114
|
-
}
|
|
115
|
-
finally {
|
|
116
|
-
this.cleanupInProgress.delete(sessionId);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
async closeAllConnections() {
|
|
120
|
-
const closePromises = Array.from(this.backingServerTransports.keys()).map((sessionId) => this.closeConnection(sessionId));
|
|
121
|
-
await Promise.all(closePromises);
|
|
122
|
-
this.logger.info(`All connections closed (${closePromises.length} total)`);
|
|
123
|
-
}
|
|
124
|
-
updateConnectionStatus(sessionId, status) {
|
|
125
|
-
const current = this.connectionStatus.get(sessionId);
|
|
126
|
-
if (current) {
|
|
127
|
-
current.status = status;
|
|
128
|
-
current.lastActivity = new Date();
|
|
129
|
-
this.connectionStatus.set(sessionId, current);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
incrementErrorCount(sessionId) {
|
|
133
|
-
const current = this.connectionStatus.get(sessionId);
|
|
134
|
-
if (current) {
|
|
135
|
-
current.errorCount += 1;
|
|
136
|
-
this.connectionStatus.set(sessionId, current);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
setupTransportEvents(sessionId, transport) {
|
|
140
|
-
// Store original handlers to preserve existing functionality
|
|
141
|
-
const originalOnClose = transport.onclose;
|
|
142
|
-
const originalOnError = transport.onerror;
|
|
143
|
-
transport.onclose = () => {
|
|
144
|
-
this.logger.info(`Transport closed for session ${sessionId}`);
|
|
145
|
-
this.updateConnectionStatus(sessionId, "disconnected");
|
|
146
|
-
this.emit("disconnection", sessionId);
|
|
147
|
-
// Call original handler if it exists
|
|
148
|
-
if (originalOnClose) {
|
|
149
|
-
originalOnClose();
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
transport.onerror = (error) => {
|
|
153
|
-
this.logger.error(`Transport error for session ${sessionId}:`, error);
|
|
154
|
-
this.updateConnectionStatus(sessionId, "error");
|
|
155
|
-
this.incrementErrorCount(sessionId);
|
|
156
|
-
this.emit("error", sessionId, error);
|
|
157
|
-
// Call original handler if it exists
|
|
158
|
-
if (originalOnError) {
|
|
159
|
-
originalOnError(error);
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
// Helper methods for StreamableHTTP transport handling
|
|
164
|
-
async createStreamableHTTPConnection(serverConfig, requestHeaders) {
|
|
165
|
-
const sessionId = await this.createConnection(serverConfig, requestHeaders);
|
|
166
|
-
const webAppTransport = new StreamableHTTPServerTransport({
|
|
167
|
-
sessionIdGenerator: () => sessionId,
|
|
168
|
-
onsessioninitialized: (newSessionId) => {
|
|
169
|
-
this.logger.info(`โจ Created streamable web app transport ${newSessionId}`);
|
|
170
|
-
this.setWebAppTransport(newSessionId, webAppTransport);
|
|
171
|
-
// Set up proxy between web app transport and backing server transport
|
|
172
|
-
const backingTransport = this.getTransport(newSessionId);
|
|
173
|
-
if (backingTransport) {
|
|
174
|
-
mcpProxy({
|
|
175
|
-
transportToClient: webAppTransport,
|
|
176
|
-
transportToServer: backingTransport,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
// Set up cleanup handler
|
|
180
|
-
webAppTransport.onclose = () => {
|
|
181
|
-
this.closeConnection(newSessionId);
|
|
182
|
-
};
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
await webAppTransport.start();
|
|
186
|
-
return { sessionId, webAppTransport };
|
|
187
|
-
}
|
|
188
|
-
// Helper method for SSE transport handling
|
|
189
|
-
async createSSEConnection(serverConfig, res, requestHeaders) {
|
|
190
|
-
const connectionId = await this.createConnection(serverConfig, requestHeaders);
|
|
191
|
-
const webAppTransport = new SSEServerTransport("/message", res);
|
|
192
|
-
const sessionId = webAppTransport.sessionId;
|
|
193
|
-
this.setWebAppTransport(sessionId, webAppTransport);
|
|
194
|
-
// Set up cleanup handler
|
|
195
|
-
webAppTransport.onclose = () => {
|
|
196
|
-
this.closeConnection(connectionId);
|
|
197
|
-
};
|
|
198
|
-
await webAppTransport.start();
|
|
199
|
-
// Set up proxy between web app transport and backing server transport
|
|
200
|
-
const backingTransport = this.getTransport(connectionId);
|
|
201
|
-
if (backingTransport) {
|
|
202
|
-
// Special handling for STDIO stderr
|
|
203
|
-
if (backingTransport instanceof StdioClientTransport) {
|
|
204
|
-
backingTransport.stderr?.on("data", (chunk) => {
|
|
205
|
-
webAppTransport.send({
|
|
206
|
-
jsonrpc: "2.0",
|
|
207
|
-
method: "stderr",
|
|
208
|
-
params: {
|
|
209
|
-
data: chunk.toString(),
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
mcpProxy({
|
|
215
|
-
transportToClient: webAppTransport,
|
|
216
|
-
transportToServer: backingTransport,
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
return { sessionId, webAppTransport };
|
|
220
|
-
}
|
|
221
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
2
|
-
import { StdioClientTransport, getDefaultEnvironment, } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4
|
-
import { findActualExecutable } from "spawn-rx";
|
|
5
|
-
import { validateServerConfig, ConsoleLogger } from "./utils.js";
|
|
6
|
-
const SSE_HEADERS_PASSTHROUGH = ["authorization"];
|
|
7
|
-
const STREAMABLE_HTTP_HEADERS_PASSTHROUGH = [
|
|
8
|
-
"authorization",
|
|
9
|
-
"mcp-session-id",
|
|
10
|
-
"last-event-id",
|
|
11
|
-
];
|
|
12
|
-
export class TransportFactory {
|
|
13
|
-
logger;
|
|
14
|
-
defaultEnvironment;
|
|
15
|
-
constructor(options = {}) {
|
|
16
|
-
this.logger = options.logger || new ConsoleLogger();
|
|
17
|
-
this.defaultEnvironment = {
|
|
18
|
-
...getDefaultEnvironment(),
|
|
19
|
-
...(process.env.MCP_ENV_VARS ? JSON.parse(process.env.MCP_ENV_VARS) : {}),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
async createTransport(config, requestHeaders) {
|
|
23
|
-
validateServerConfig(config);
|
|
24
|
-
this.logger.info(`Creating ${config.type} transport for ${config.name}`);
|
|
25
|
-
try {
|
|
26
|
-
switch (config.type) {
|
|
27
|
-
case "stdio":
|
|
28
|
-
return await this.createStdioTransport(config);
|
|
29
|
-
case "sse":
|
|
30
|
-
return await this.createSSETransport(config, requestHeaders);
|
|
31
|
-
case "streamable-http":
|
|
32
|
-
return await this.createStreamableHTTPTransport(config, requestHeaders);
|
|
33
|
-
default:
|
|
34
|
-
throw new Error(`Unsupported transport type: ${config.type}`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
this.logger.error(`Failed to create transport for ${config.name}:`, error);
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
async createStdioTransport(config) {
|
|
43
|
-
const command = config.command;
|
|
44
|
-
const origArgs = config.args || [];
|
|
45
|
-
const queryEnv = config.env || {};
|
|
46
|
-
// Filter out undefined values from process.env
|
|
47
|
-
const processEnv = Object.fromEntries(Object.entries(process.env).filter(([, value]) => value !== undefined));
|
|
48
|
-
const env = { ...processEnv, ...this.defaultEnvironment, ...queryEnv };
|
|
49
|
-
const { cmd, args } = findActualExecutable(command, origArgs);
|
|
50
|
-
this.logger.info(`๐ Stdio transport: command=${cmd}, args=${args}`);
|
|
51
|
-
const transport = new StdioClientTransport({
|
|
52
|
-
command: cmd,
|
|
53
|
-
args,
|
|
54
|
-
env,
|
|
55
|
-
stderr: "pipe",
|
|
56
|
-
});
|
|
57
|
-
await this.setupTransportLifecycle(transport, config.id);
|
|
58
|
-
await transport.start();
|
|
59
|
-
return transport;
|
|
60
|
-
}
|
|
61
|
-
async createSSETransport(config, requestHeaders) {
|
|
62
|
-
const url = config.url;
|
|
63
|
-
const headers = {
|
|
64
|
-
Accept: "text/event-stream",
|
|
65
|
-
...config.headers,
|
|
66
|
-
};
|
|
67
|
-
// Add headers passed through from the request
|
|
68
|
-
if (requestHeaders) {
|
|
69
|
-
for (const key of SSE_HEADERS_PASSTHROUGH) {
|
|
70
|
-
if (requestHeaders[key] !== undefined) {
|
|
71
|
-
headers[key] = requestHeaders[key];
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
this.logger.info(`๐ SSE transport: url=${url}`);
|
|
76
|
-
const transport = new SSEClientTransport(new URL(url), {
|
|
77
|
-
eventSourceInit: {
|
|
78
|
-
fetch: (url, init) => fetch(url, { ...init, headers }),
|
|
79
|
-
},
|
|
80
|
-
requestInit: {
|
|
81
|
-
headers,
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
await this.setupTransportLifecycle(transport, config.id);
|
|
85
|
-
await transport.start();
|
|
86
|
-
return transport;
|
|
87
|
-
}
|
|
88
|
-
async createStreamableHTTPTransport(config, requestHeaders) {
|
|
89
|
-
const url = config.url;
|
|
90
|
-
const headers = {
|
|
91
|
-
Accept: "text/event-stream, application/json",
|
|
92
|
-
...config.headers,
|
|
93
|
-
};
|
|
94
|
-
// Add headers passed through from the request
|
|
95
|
-
if (requestHeaders) {
|
|
96
|
-
for (const key of STREAMABLE_HTTP_HEADERS_PASSTHROUGH) {
|
|
97
|
-
if (requestHeaders[key] !== undefined) {
|
|
98
|
-
headers[key] = requestHeaders[key];
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
this.logger.info(`๐ StreamableHTTP transport: url=${url}`);
|
|
103
|
-
const transport = new StreamableHTTPClientTransport(new URL(url), {
|
|
104
|
-
requestInit: {
|
|
105
|
-
headers,
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
await this.setupTransportLifecycle(transport, config.id);
|
|
109
|
-
await transport.start();
|
|
110
|
-
return transport;
|
|
111
|
-
}
|
|
112
|
-
async setupTransportLifecycle(transport, configId) {
|
|
113
|
-
// Set up event handlers without aggressive timeouts
|
|
114
|
-
// The original server didn't have connection timeouts, so we preserve that behavior
|
|
115
|
-
const originalOnClose = transport.onclose;
|
|
116
|
-
transport.onclose = () => {
|
|
117
|
-
this.logger.info(`Transport closed for ${configId}`);
|
|
118
|
-
if (originalOnClose) {
|
|
119
|
-
originalOnClose();
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
const originalOnError = transport.onerror;
|
|
123
|
-
transport.onerror = (error) => {
|
|
124
|
-
this.logger.error(`Transport error for ${configId}:`, error);
|
|
125
|
-
if (originalOnError) {
|
|
126
|
-
originalOnError(error);
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "crypto";
|
|
2
|
-
export function generateSessionId() {
|
|
3
|
-
return randomUUID();
|
|
4
|
-
}
|
|
5
|
-
export function validateServerConfig(config) {
|
|
6
|
-
if (!config.id || !config.type || !config.name) {
|
|
7
|
-
throw new Error("Invalid server configuration: id, type, and name are required");
|
|
8
|
-
}
|
|
9
|
-
if (config.type === "stdio" && !config.command) {
|
|
10
|
-
throw new Error("STDIO transport requires command");
|
|
11
|
-
}
|
|
12
|
-
if ((config.type === "sse" || config.type === "streamable-http") &&
|
|
13
|
-
!config.url) {
|
|
14
|
-
throw new Error("SSE and StreamableHTTP transports require URL");
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
export class ConsoleLogger {
|
|
18
|
-
info(message, ...args) {
|
|
19
|
-
console.log(`[INFO] ${message}`, ...args);
|
|
20
|
-
}
|
|
21
|
-
error(message, ...args) {
|
|
22
|
-
console.error(`[ERROR] ${message}`, ...args);
|
|
23
|
-
}
|
|
24
|
-
warn(message, ...args) {
|
|
25
|
-
console.warn(`[WARN] ${message}`, ...args);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { TestServer } from "./testing/TestServer.js";
|
|
3
|
-
import { MCPProxyService } from "./shared/MCPProxyService.js";
|
|
4
|
-
import { DatabaseManager } from "./database/DatabaseManager.js";
|
|
5
|
-
import { ConsoleLogger } from "./shared/utils.js";
|
|
6
|
-
import { getDatabaseConfig } from "./database/utils.js";
|
|
7
|
-
import { createServer } from "node:net";
|
|
8
|
-
import { parseArgs } from "node:util";
|
|
9
|
-
// Function to find an available port
|
|
10
|
-
const findAvailablePort = async (startPort) => {
|
|
11
|
-
return new Promise((resolve, reject) => {
|
|
12
|
-
const server = createServer();
|
|
13
|
-
server.listen(startPort, () => {
|
|
14
|
-
const port = server.address()?.port;
|
|
15
|
-
server.close(() => {
|
|
16
|
-
resolve(port);
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
server.on("error", (err) => {
|
|
20
|
-
if (err.code === "EADDRINUSE") {
|
|
21
|
-
// Port is in use, try the next one
|
|
22
|
-
findAvailablePort(startPort + 1)
|
|
23
|
-
.then(resolve)
|
|
24
|
-
.catch(reject);
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
reject(err);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
};
|
|
32
|
-
async function startTestServer() {
|
|
33
|
-
const logger = new ConsoleLogger();
|
|
34
|
-
// Parse command line arguments
|
|
35
|
-
const { values } = parseArgs({
|
|
36
|
-
args: process.argv.slice(2),
|
|
37
|
-
options: {
|
|
38
|
-
port: { type: "string", short: "p", default: "3002" },
|
|
39
|
-
host: { type: "string", short: "h", default: "localhost" },
|
|
40
|
-
env: { type: "string", short: "e", default: "development" },
|
|
41
|
-
config: { type: "string", short: "c" },
|
|
42
|
-
help: { type: "boolean", default: false },
|
|
43
|
-
},
|
|
44
|
-
allowPositionals: true,
|
|
45
|
-
});
|
|
46
|
-
if (values.help) {
|
|
47
|
-
console.log(`
|
|
48
|
-
๐งช MCPJam Inspector Test Server
|
|
49
|
-
|
|
50
|
-
Usage: node test-server.js [options]
|
|
51
|
-
|
|
52
|
-
Options:
|
|
53
|
-
-p, --port <port> Port to run the test server on (default: 3002)
|
|
54
|
-
-h, --host <host> Host to bind the server to (default: localhost)
|
|
55
|
-
-e, --env <env> Environment mode (default: development)
|
|
56
|
-
-c, --config <file> Configuration file path
|
|
57
|
-
--help Show this help message
|
|
58
|
-
|
|
59
|
-
Environment Variables:
|
|
60
|
-
TEST_PORT Override the default port
|
|
61
|
-
TEST_HOST Override the default host
|
|
62
|
-
NODE_ENV Set the environment mode
|
|
63
|
-
DATABASE_URL Database connection string
|
|
64
|
-
LOG_LEVEL Logging level (debug, info, warn, error)
|
|
65
|
-
|
|
66
|
-
Examples:
|
|
67
|
-
node test-server.js --port 4000 --host 0.0.0.0
|
|
68
|
-
TEST_PORT=5000 node test-server.js
|
|
69
|
-
NODE_ENV=production node test-server.js
|
|
70
|
-
`);
|
|
71
|
-
process.exit(0);
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
// Initialize core services
|
|
75
|
-
const mcpProxyService = new MCPProxyService({
|
|
76
|
-
logger,
|
|
77
|
-
maxConnections: 100, // Higher limit for test server
|
|
78
|
-
});
|
|
79
|
-
const dbConfig = getDatabaseConfig();
|
|
80
|
-
const database = new DatabaseManager(dbConfig);
|
|
81
|
-
await database.initialize();
|
|
82
|
-
// Determine port with fallback logic
|
|
83
|
-
const preferredPort = process.env.TEST_PORT
|
|
84
|
-
? parseInt(process.env.TEST_PORT)
|
|
85
|
-
: parseInt(values.port);
|
|
86
|
-
const actualPort = await findAvailablePort(preferredPort);
|
|
87
|
-
// Create and start test server
|
|
88
|
-
const testServer = new TestServer({
|
|
89
|
-
port: actualPort,
|
|
90
|
-
host: process.env.TEST_HOST || values.host,
|
|
91
|
-
cors: values.env !== "production",
|
|
92
|
-
rateLimiting: true,
|
|
93
|
-
database: {
|
|
94
|
-
url: process.env.DATABASE_URL || "sqlite://test.db",
|
|
95
|
-
maxConnections: 10,
|
|
96
|
-
timeout: 5000,
|
|
97
|
-
},
|
|
98
|
-
logging: {
|
|
99
|
-
level: process.env.LOG_LEVEL || "info",
|
|
100
|
-
format: "json",
|
|
101
|
-
outputs: ["console"],
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
await testServer.start(mcpProxyService, database);
|
|
105
|
-
if (actualPort !== preferredPort) {
|
|
106
|
-
logger.info(`โ ๏ธ Port ${preferredPort} was in use, using available port ${actualPort} instead`);
|
|
107
|
-
}
|
|
108
|
-
logger.info(`๐งช Test server started on http://${testServer.config.host}:${testServer.config.port}`);
|
|
109
|
-
logger.info(`๐ Health check available at http://${testServer.config.host}:${testServer.config.port}/api/test/health`);
|
|
110
|
-
logger.info(`๐ Status endpoint available at http://${testServer.config.host}:${testServer.config.port}/api/test/status`);
|
|
111
|
-
logger.info(`๐ฏ Test execution endpoint available at http://${testServer.config.host}:${testServer.config.port}/api/test/run`);
|
|
112
|
-
// Graceful shutdown
|
|
113
|
-
const shutdown = async (signal) => {
|
|
114
|
-
logger.info(`๐ Received ${signal}, shutting down test server...`);
|
|
115
|
-
try {
|
|
116
|
-
await testServer.stop();
|
|
117
|
-
await mcpProxyService.closeAllConnections();
|
|
118
|
-
await database.close();
|
|
119
|
-
logger.info("โ
Test server shutdown complete");
|
|
120
|
-
process.exit(0);
|
|
121
|
-
}
|
|
122
|
-
catch (error) {
|
|
123
|
-
logger.error("โ Error during shutdown:", error);
|
|
124
|
-
process.exit(1);
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
128
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
129
|
-
// Handle unhandled promise rejections
|
|
130
|
-
process.on("unhandledRejection", (reason, promise) => {
|
|
131
|
-
logger.error("โ Unhandled Rejection at:", promise, "reason:", reason);
|
|
132
|
-
});
|
|
133
|
-
process.on("uncaughtException", (error) => {
|
|
134
|
-
logger.error("โ Uncaught Exception:", error);
|
|
135
|
-
process.exit(1);
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
logger.error("โ Failed to start test server:", error);
|
|
140
|
-
process.exit(1);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
144
|
-
startTestServer();
|
|
145
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export class HealthCheck {
|
|
2
|
-
mcpProxyService;
|
|
3
|
-
database;
|
|
4
|
-
logger;
|
|
5
|
-
constructor(mcpProxyService, database, logger) {
|
|
6
|
-
this.mcpProxyService = mcpProxyService;
|
|
7
|
-
this.database = database;
|
|
8
|
-
this.logger = logger;
|
|
9
|
-
}
|
|
10
|
-
getStatus() {
|
|
11
|
-
return {
|
|
12
|
-
testing: true,
|
|
13
|
-
status: "healthy",
|
|
14
|
-
version: process.env.npm_package_version || "0.3.5",
|
|
15
|
-
timestamp: new Date().toISOString(),
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
getDetailedStatus() {
|
|
19
|
-
return {
|
|
20
|
-
testing: true,
|
|
21
|
-
server: {
|
|
22
|
-
status: "running",
|
|
23
|
-
uptime: process.uptime(),
|
|
24
|
-
memory: process.memoryUsage(),
|
|
25
|
-
version: process.env.npm_package_version || "0.3.5",
|
|
26
|
-
},
|
|
27
|
-
connections: {
|
|
28
|
-
active: this.mcpProxyService.getActiveConnections().length,
|
|
29
|
-
list: this.mcpProxyService.getActiveConnections(),
|
|
30
|
-
},
|
|
31
|
-
database: {
|
|
32
|
-
connected: this.isDatabaseConnected(),
|
|
33
|
-
status: this.isDatabaseConnected() ? "connected" : "disconnected",
|
|
34
|
-
},
|
|
35
|
-
timestamp: new Date().toISOString(),
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
isDatabaseConnected() {
|
|
39
|
-
// Simple check - in a real implementation you might want to ping the database
|
|
40
|
-
return this.database !== null && this.database !== undefined;
|
|
41
|
-
}
|
|
42
|
-
}
|