@mcp-shark/mcp-shark 1.4.1 → 1.4.2
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/mcp-server/lib/auditor/audit.js +12 -4
- package/mcp-server/lib/server/external/kv.js +17 -28
- package/mcp-server/lib/server/internal/handlers/prompts-get.js +14 -6
- package/mcp-server/lib/server/internal/handlers/prompts-list.js +9 -4
- package/mcp-server/lib/server/internal/handlers/resources-list.js +9 -4
- package/mcp-server/lib/server/internal/handlers/resources-read.js +13 -3
- package/mcp-server/lib/server/internal/handlers/tools-call.js +12 -8
- package/mcp-server/lib/server/internal/handlers/tools-list.js +4 -3
- package/mcp-server/lib/server/internal/run.js +1 -1
- package/mcp-server/lib/server/internal/server.js +10 -10
- package/mcp-server/lib/server/internal/session.js +14 -6
- package/package.json +1 -1
- package/ui/server/routes/composite.js +16 -0
- package/ui/server/routes/playground.js +45 -14
- package/ui/server/utils/config-update.js +136 -109
- package/ui/server.js +1 -0
- package/ui/src/components/GroupedByMcpView.jsx +0 -6
- package/ui/src/components/McpPlayground/PromptsSection/PromptItem.jsx +46 -21
- package/ui/src/components/McpPlayground/PromptsSection/PromptsList.jsx +10 -8
- package/ui/src/components/McpPlayground/ResourcesSection/ResourceItem.jsx +60 -32
- package/ui/src/components/McpPlayground/ResourcesSection/ResourcesList.jsx +10 -8
- package/ui/src/components/McpPlayground/ToolsSection/ToolItem.jsx +46 -21
- package/ui/src/components/McpPlayground/ToolsSection/ToolsList.jsx +10 -8
- package/ui/src/components/McpPlayground/hooks/useMcpDataLoader.js +107 -0
- package/ui/src/components/McpPlayground/hooks/useMcpRequest.js +65 -0
- package/ui/src/components/McpPlayground/hooks/useMcpServerStatus.js +70 -0
- package/ui/src/components/McpPlayground/useMcpPlayground.js +68 -137
- package/ui/src/components/McpPlayground.jsx +100 -21
- package/ui/src/utils/requestUtils.js +5 -4
|
@@ -135,14 +135,22 @@ export async function withAuditRequestResponseHandler(
|
|
|
135
135
|
transport,
|
|
136
136
|
req,
|
|
137
137
|
res,
|
|
138
|
-
auditLogger
|
|
138
|
+
auditLogger,
|
|
139
|
+
requestedMcpServer,
|
|
140
|
+
initialSessionId
|
|
139
141
|
) {
|
|
140
142
|
const reqBuf = await readBody(req);
|
|
141
143
|
const reqJsonRpc = tryParseJsonRpc(reqBuf);
|
|
142
144
|
|
|
143
145
|
// Extract session ID from request
|
|
144
146
|
// If no session ID exists, it's an initiation request
|
|
145
|
-
const
|
|
147
|
+
const sessionIdFromRequest = getSessionFromRequest(req);
|
|
148
|
+
const sessionId =
|
|
149
|
+
sessionIdFromRequest === null ||
|
|
150
|
+
sessionIdFromRequest === undefined ||
|
|
151
|
+
sessionIdFromRequest === ''
|
|
152
|
+
? initialSessionId
|
|
153
|
+
: sessionIdFromRequest;
|
|
146
154
|
|
|
147
155
|
// Extract request body as string
|
|
148
156
|
const reqBodyStr = reqBuf.toString('utf8');
|
|
@@ -161,7 +169,7 @@ export async function withAuditRequestResponseHandler(
|
|
|
161
169
|
headers: req.headers,
|
|
162
170
|
body: reqBodyJson || reqBodyStr,
|
|
163
171
|
userAgent: req.headers['user-agent'] || req.headers['User-Agent'] || null,
|
|
164
|
-
remoteAddress:
|
|
172
|
+
remoteAddress: requestedMcpServer,
|
|
165
173
|
sessionId: sessionId || null,
|
|
166
174
|
});
|
|
167
175
|
|
|
@@ -223,6 +231,6 @@ export async function withAuditRequestResponseHandler(
|
|
|
223
231
|
jsonrpcId,
|
|
224
232
|
sessionId: sessionId || null,
|
|
225
233
|
userAgent: req.headers['user-agent'] || req.headers['User-Agent'] || null,
|
|
226
|
-
remoteAddress:
|
|
234
|
+
remoteAddress: requestedMcpServer || null,
|
|
227
235
|
});
|
|
228
236
|
}
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
const kv = new Map();
|
|
2
2
|
|
|
3
|
-
function buildName(name, typeName) {
|
|
4
|
-
return `${name}.${typeName}`;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function extractName(name) {
|
|
8
|
-
const [serverName, typeName] = name.split('.');
|
|
9
|
-
return { serverName, typeName };
|
|
10
|
-
}
|
|
11
|
-
|
|
12
3
|
export function buildKv(downstreamServers) {
|
|
13
4
|
for (const downstreamServer of downstreamServers) {
|
|
14
5
|
const {
|
|
@@ -43,16 +34,16 @@ export function buildKv(downstreamServers) {
|
|
|
43
34
|
resourcesMap,
|
|
44
35
|
promptsMap,
|
|
45
36
|
tools: tools.map(tool => {
|
|
46
|
-
return { ...tool, name:
|
|
37
|
+
return { ...tool, name: tool.name };
|
|
47
38
|
}),
|
|
48
39
|
resources: resources.map(resource => {
|
|
49
40
|
return {
|
|
50
41
|
...resource,
|
|
51
|
-
name:
|
|
42
|
+
name: resource.name,
|
|
52
43
|
};
|
|
53
44
|
}),
|
|
54
45
|
prompts: prompts.map(prompt => {
|
|
55
|
-
return { ...prompt, name:
|
|
46
|
+
return { ...prompt, name: prompt.name };
|
|
56
47
|
}),
|
|
57
48
|
});
|
|
58
49
|
}
|
|
@@ -61,42 +52,40 @@ export function buildKv(downstreamServers) {
|
|
|
61
52
|
return kv;
|
|
62
53
|
}
|
|
63
54
|
|
|
64
|
-
export function getBy(database, calledName, action) {
|
|
65
|
-
const
|
|
66
|
-
if (!serverName || !typeName) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
const entry = database.get(serverName);
|
|
55
|
+
export function getBy(database, requestedMcpServer, calledName, action) {
|
|
56
|
+
const entry = database.get(requestedMcpServer);
|
|
70
57
|
if (!entry) {
|
|
71
58
|
return null;
|
|
72
59
|
}
|
|
73
60
|
|
|
74
61
|
// Type-based lookup
|
|
75
62
|
if (action === 'getTools') {
|
|
76
|
-
return entry.toolsMap.get(
|
|
63
|
+
return entry.toolsMap.get(calledName);
|
|
77
64
|
}
|
|
78
65
|
if (action === 'getResources') {
|
|
79
|
-
return entry.resourcesMap.get(
|
|
66
|
+
return entry.resourcesMap.get(calledName);
|
|
80
67
|
}
|
|
81
68
|
if (action === 'getPrompts') {
|
|
82
|
-
return entry.promptsMap.get(
|
|
69
|
+
return entry.promptsMap.get(calledName);
|
|
83
70
|
}
|
|
84
71
|
|
|
85
72
|
// Action-based lookup
|
|
86
73
|
if (action === 'callTool') {
|
|
87
|
-
return entry.toolsMap.get(
|
|
74
|
+
return entry.toolsMap.get(calledName);
|
|
88
75
|
}
|
|
89
76
|
if (action === 'readResource') {
|
|
90
|
-
return entry.resourcesMap.get(
|
|
77
|
+
return entry.resourcesMap.get(calledName);
|
|
91
78
|
}
|
|
92
79
|
if (action === 'getPrompt') {
|
|
93
|
-
return entry.promptsMap.get(
|
|
80
|
+
return entry.promptsMap.get(calledName);
|
|
94
81
|
}
|
|
95
82
|
return null;
|
|
96
83
|
}
|
|
97
84
|
|
|
98
|
-
export function listAll(database, type) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
85
|
+
export function listAll(database, requestedMcpServer, type) {
|
|
86
|
+
const serverEntry = database.get(requestedMcpServer);
|
|
87
|
+
if (!serverEntry) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
return serverEntry[type];
|
|
102
91
|
}
|
|
@@ -1,20 +1,28 @@
|
|
|
1
|
-
import { getBy
|
|
1
|
+
import { getBy } from '../../external/kv.js';
|
|
2
2
|
import { InternalServerError } from './error.js';
|
|
3
3
|
|
|
4
|
-
export function createPromptsGetHandler(
|
|
4
|
+
export function createPromptsGetHandler(
|
|
5
|
+
logger,
|
|
6
|
+
mcpServers,
|
|
7
|
+
requestedMcpServer
|
|
8
|
+
) {
|
|
5
9
|
return async req => {
|
|
6
10
|
const name = req.params.name;
|
|
7
11
|
const promptArgs = req?.params?.arguments || {};
|
|
8
12
|
logger.debug('Prompt get', name, promptArgs);
|
|
9
13
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
const getPrompt = getBy(
|
|
15
|
+
mcpServers,
|
|
16
|
+
requestedMcpServer,
|
|
17
|
+
name,
|
|
18
|
+
'getPrompt',
|
|
19
|
+
promptArgs
|
|
20
|
+
);
|
|
13
21
|
if (!getPrompt) {
|
|
14
22
|
throw new InternalServerError(`Prompt not found: ${name}`);
|
|
15
23
|
}
|
|
16
24
|
|
|
17
|
-
const result = await getPrompt(
|
|
25
|
+
const result = await getPrompt(name, promptArgs);
|
|
18
26
|
logger.debug('Prompt get result', result);
|
|
19
27
|
|
|
20
28
|
return result;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { listAll } from '../../external/kv.js';
|
|
2
2
|
|
|
3
|
-
export function createPromptsListHandler(
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export function createPromptsListHandler(
|
|
4
|
+
logger,
|
|
5
|
+
mcpServers,
|
|
6
|
+
requestedMcpServer
|
|
7
|
+
) {
|
|
8
|
+
return async req => {
|
|
9
|
+
const path = req.path;
|
|
10
|
+
logger.debug('Prompts list', path);
|
|
6
11
|
|
|
7
|
-
const res = await listAll(mcpServers, 'prompts');
|
|
12
|
+
const res = await listAll(mcpServers, requestedMcpServer, 'prompts');
|
|
8
13
|
const result = Array.isArray(res) ? { prompts: res } : res;
|
|
9
14
|
|
|
10
15
|
return result;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { listAll } from '../../external/kv.js';
|
|
2
2
|
|
|
3
|
-
export function createResourcesListHandler(
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export function createResourcesListHandler(
|
|
4
|
+
logger,
|
|
5
|
+
mcpServers,
|
|
6
|
+
requestedMcpServer
|
|
7
|
+
) {
|
|
8
|
+
return async req => {
|
|
9
|
+
const path = req.path;
|
|
10
|
+
logger.debug('Resources list', path);
|
|
6
11
|
|
|
7
|
-
const res = await listAll(mcpServers, 'resources');
|
|
12
|
+
const res = await listAll(mcpServers, requestedMcpServer, 'resources');
|
|
8
13
|
const result = Array.isArray(res) ? { resources: res } : res;
|
|
9
14
|
|
|
10
15
|
return result;
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { getBy } from '../../external/kv.js';
|
|
2
2
|
import { InternalServerError } from './error.js';
|
|
3
3
|
|
|
4
|
-
export function createResourcesReadHandler(
|
|
4
|
+
export function createResourcesReadHandler(
|
|
5
|
+
logger,
|
|
6
|
+
mcpServers,
|
|
7
|
+
requestedMcpServer
|
|
8
|
+
) {
|
|
5
9
|
return async req => {
|
|
10
|
+
const path = req.path;
|
|
6
11
|
const uri = req.params.uri;
|
|
7
|
-
logger.debug('Resource read', uri);
|
|
12
|
+
logger.debug('Resource read', path, uri);
|
|
8
13
|
|
|
9
|
-
const readResource = getBy(
|
|
14
|
+
const readResource = getBy(
|
|
15
|
+
mcpServers,
|
|
16
|
+
requestedMcpServer,
|
|
17
|
+
uri,
|
|
18
|
+
'readResource'
|
|
19
|
+
);
|
|
10
20
|
if (!readResource) {
|
|
11
21
|
throw new InternalServerError(`Resource not found: ${uri}`);
|
|
12
22
|
}
|
|
@@ -1,24 +1,28 @@
|
|
|
1
|
-
import { getBy
|
|
1
|
+
import { getBy } from '../../external/kv.js';
|
|
2
2
|
import { InternalServerError } from './error.js';
|
|
3
3
|
|
|
4
4
|
const isAsyncIterable = v => v && typeof v[Symbol.asyncIterator] === 'function';
|
|
5
5
|
|
|
6
|
-
export function createToolsCallHandler(logger, mcpServers) {
|
|
6
|
+
export function createToolsCallHandler(logger, mcpServers, requestedMcpServer) {
|
|
7
7
|
return async req => {
|
|
8
|
+
const path = req.path;
|
|
8
9
|
const { name, arguments: args } = req.params;
|
|
9
|
-
logger.debug('Tool call', name, args);
|
|
10
|
+
logger.debug('Tool call', path, name, args);
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const callTool = getBy(
|
|
13
|
+
mcpServers,
|
|
14
|
+
requestedMcpServer,
|
|
15
|
+
name,
|
|
16
|
+
'callTool',
|
|
17
|
+
args || {}
|
|
18
|
+
);
|
|
15
19
|
if (!callTool) {
|
|
16
20
|
throw new InternalServerError(`Tool not found: ${name}`);
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
const result = await callTool({
|
|
20
24
|
...req.params,
|
|
21
|
-
name
|
|
25
|
+
name,
|
|
22
26
|
});
|
|
23
27
|
logger.debug('Tool call result', result);
|
|
24
28
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { listAll } from '../../external/kv.js';
|
|
2
2
|
|
|
3
|
-
export function createToolsListHandler(logger, mcpServers) {
|
|
3
|
+
export function createToolsListHandler(logger, mcpServers, requestedMcpServer) {
|
|
4
4
|
return async req => {
|
|
5
|
-
|
|
5
|
+
const path = req.path;
|
|
6
|
+
logger.debug('Listing tools', path);
|
|
6
7
|
|
|
7
|
-
const res = await listAll(mcpServers, 'tools');
|
|
8
|
+
const res = await listAll(mcpServers, requestedMcpServer, 'tools');
|
|
8
9
|
logger.debug('Tools list result', res);
|
|
9
10
|
|
|
10
11
|
const result = Array.isArray(res) ? { tools: res } : res;
|
|
@@ -15,49 +15,49 @@ import { createPromptsListHandler } from './handlers/prompts-list.js';
|
|
|
15
15
|
import { createPromptsGetHandler } from './handlers/prompts-get.js';
|
|
16
16
|
import { createResourcesListHandler } from './handlers/resources-list.js';
|
|
17
17
|
import { createResourcesReadHandler } from './handlers/resources-read.js';
|
|
18
|
-
import { SERVER_NAME } from './handlers/common.js';
|
|
19
18
|
|
|
20
|
-
export function createInternalServer(logger, mcpServers) {
|
|
19
|
+
export function createInternalServer(logger, mcpServers, requestedMcpServer) {
|
|
21
20
|
// create MCP server
|
|
22
21
|
const server = new Server(
|
|
23
|
-
{ name:
|
|
22
|
+
{ name: requestedMcpServer, version: '1.0.0' },
|
|
24
23
|
{ capabilities: { tools: {}, prompts: {}, resources: {} } }
|
|
25
24
|
);
|
|
26
25
|
|
|
27
26
|
// Register handlers
|
|
28
27
|
server.setRequestHandler(
|
|
29
28
|
ListToolsRequestSchema,
|
|
30
|
-
createToolsListHandler(logger, mcpServers)
|
|
29
|
+
createToolsListHandler(logger, mcpServers, requestedMcpServer)
|
|
31
30
|
);
|
|
32
31
|
|
|
33
32
|
server.setRequestHandler(
|
|
34
33
|
CallToolRequestSchema,
|
|
35
|
-
createToolsCallHandler(logger, mcpServers)
|
|
34
|
+
createToolsCallHandler(logger, mcpServers, requestedMcpServer)
|
|
36
35
|
);
|
|
37
36
|
|
|
38
37
|
server.setRequestHandler(
|
|
39
38
|
ListPromptsRequestSchema,
|
|
40
|
-
createPromptsListHandler(logger, mcpServers)
|
|
39
|
+
createPromptsListHandler(logger, mcpServers, requestedMcpServer)
|
|
41
40
|
);
|
|
42
41
|
|
|
43
42
|
server.setRequestHandler(
|
|
44
43
|
GetPromptRequestSchema,
|
|
45
|
-
createPromptsGetHandler(logger, mcpServers)
|
|
44
|
+
createPromptsGetHandler(logger, mcpServers, requestedMcpServer)
|
|
46
45
|
);
|
|
47
46
|
|
|
48
47
|
server.setRequestHandler(
|
|
49
48
|
ListResourcesRequestSchema,
|
|
50
|
-
createResourcesListHandler(logger, mcpServers)
|
|
49
|
+
createResourcesListHandler(logger, mcpServers, requestedMcpServer)
|
|
51
50
|
);
|
|
52
51
|
|
|
53
52
|
server.setRequestHandler(
|
|
54
53
|
ReadResourceRequestSchema,
|
|
55
|
-
createResourcesReadHandler(logger, mcpServers)
|
|
54
|
+
createResourcesReadHandler(logger, mcpServers, requestedMcpServer)
|
|
56
55
|
);
|
|
57
56
|
|
|
58
57
|
return server;
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
export function createInternalServerFactory(logger, mcpServers) {
|
|
62
|
-
return
|
|
61
|
+
return requestedMcpServer =>
|
|
62
|
+
createInternalServer(logger, mcpServers, requestedMcpServer);
|
|
63
63
|
}
|
|
@@ -20,20 +20,28 @@ export async function withSession(
|
|
|
20
20
|
res,
|
|
21
21
|
auditLogger
|
|
22
22
|
) {
|
|
23
|
+
const requestedMcpServer = req.params[0];
|
|
23
24
|
const sessionId = getSessionFromRequest(req);
|
|
24
25
|
if (!sessionId) {
|
|
25
|
-
const
|
|
26
|
+
const initialSessionId = randomUUID();
|
|
26
27
|
const transport = new StreamableHTTPServerTransport({
|
|
27
|
-
sessionIdGenerator: () =>
|
|
28
|
+
sessionIdGenerator: () => initialSessionId,
|
|
28
29
|
enableJsonResponse: true,
|
|
29
30
|
});
|
|
30
|
-
const server = serverFactory();
|
|
31
|
+
const server = serverFactory(requestedMcpServer);
|
|
31
32
|
await server.connect(transport);
|
|
32
|
-
storeTransportInSession(
|
|
33
|
+
storeTransportInSession(initialSessionId, transport);
|
|
33
34
|
// Session creation will be logged as part of the request packet in audit.js
|
|
34
|
-
return requestHandler(
|
|
35
|
+
return requestHandler(
|
|
36
|
+
transport,
|
|
37
|
+
req,
|
|
38
|
+
res,
|
|
39
|
+
auditLogger,
|
|
40
|
+
requestedMcpServer,
|
|
41
|
+
initialSessionId
|
|
42
|
+
);
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
const transport = getTransportFromSession(sessionId);
|
|
38
|
-
return requestHandler(transport, req, res, auditLogger);
|
|
46
|
+
return requestHandler(transport, req, res, auditLogger, requestedMcpServer);
|
|
39
47
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-shark/mcp-shark",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "Aggregate multiple Model Context Protocol (MCP) servers into a single unified interface with a powerful monitoring UI. Prov deep visibility into every request and response.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/mcp-shark.js",
|
|
@@ -240,5 +240,21 @@ export function createCompositeRoutes(
|
|
|
240
240
|
});
|
|
241
241
|
};
|
|
242
242
|
|
|
243
|
+
router.getServers = (req, res) => {
|
|
244
|
+
try {
|
|
245
|
+
const mcpsJsonPath = getMcpConfigPath();
|
|
246
|
+
if (!fs.existsSync(mcpsJsonPath)) {
|
|
247
|
+
return res.json({ servers: [] });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const configContent = fs.readFileSync(mcpsJsonPath, 'utf-8');
|
|
251
|
+
const config = JSON.parse(configContent);
|
|
252
|
+
const servers = config.servers ? Object.keys(config.servers) : [];
|
|
253
|
+
res.json({ servers });
|
|
254
|
+
} catch (error) {
|
|
255
|
+
res.status(500).json({ error: 'Failed to get servers', details: error.message });
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
243
259
|
return router;
|
|
244
260
|
}
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
2
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const MCP_SERVER_BASE_URL = 'http://localhost:9851/mcp';
|
|
5
5
|
|
|
6
|
-
// Store client connections per session
|
|
6
|
+
// Store client connections per server and session
|
|
7
7
|
const clientSessions = new Map();
|
|
8
8
|
|
|
9
|
+
function getSessionKey(serverName, sessionId) {
|
|
10
|
+
return `${serverName}:${sessionId}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
export function createPlaygroundRoutes() {
|
|
10
14
|
const router = {};
|
|
11
15
|
|
|
12
|
-
// Get or create client for a session
|
|
13
|
-
async function getClient(sessionId) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
// Get or create client for a session and server
|
|
17
|
+
async function getClient(serverName, sessionId) {
|
|
18
|
+
const sessionKey = getSessionKey(serverName, sessionId);
|
|
19
|
+
if (clientSessions.has(sessionKey)) {
|
|
20
|
+
return clientSessions.get(sessionKey);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!serverName) {
|
|
24
|
+
throw new Error('Server name is required');
|
|
16
25
|
}
|
|
17
26
|
|
|
27
|
+
const mcpServerUrl = `${MCP_SERVER_BASE_URL}/${encodeURIComponent(serverName)}`;
|
|
28
|
+
|
|
18
29
|
const client = new Client(
|
|
19
30
|
{ name: 'mcp-shark-playground', version: '1.0.0' },
|
|
20
31
|
{
|
|
@@ -26,7 +37,7 @@ export function createPlaygroundRoutes() {
|
|
|
26
37
|
}
|
|
27
38
|
);
|
|
28
39
|
|
|
29
|
-
const transport = new StreamableHTTPClientTransport(new URL(
|
|
40
|
+
const transport = new StreamableHTTPClientTransport(new URL(mcpServerUrl));
|
|
30
41
|
await client.connect(transport);
|
|
31
42
|
|
|
32
43
|
const clientWrapper = {
|
|
@@ -38,13 +49,13 @@ export function createPlaygroundRoutes() {
|
|
|
38
49
|
},
|
|
39
50
|
};
|
|
40
51
|
|
|
41
|
-
clientSessions.set(
|
|
52
|
+
clientSessions.set(sessionKey, clientWrapper);
|
|
42
53
|
return clientWrapper;
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
router.proxyRequest = async (req, res) => {
|
|
46
57
|
try {
|
|
47
|
-
const { method, params } = req.body;
|
|
58
|
+
const { method, params, serverName } = req.body;
|
|
48
59
|
|
|
49
60
|
if (!method) {
|
|
50
61
|
return res.status(400).json({
|
|
@@ -53,13 +64,20 @@ export function createPlaygroundRoutes() {
|
|
|
53
64
|
});
|
|
54
65
|
}
|
|
55
66
|
|
|
67
|
+
if (!serverName) {
|
|
68
|
+
return res.status(400).json({
|
|
69
|
+
error: 'Invalid request',
|
|
70
|
+
message: 'serverName field is required',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
56
74
|
// Get or create session ID
|
|
57
75
|
const sessionId =
|
|
58
76
|
req.headers['mcp-session-id'] ||
|
|
59
77
|
req.headers['x-mcp-session-id'] ||
|
|
60
78
|
`playground-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
61
79
|
|
|
62
|
-
const { client } = await getClient(sessionId);
|
|
80
|
+
const { client } = await getClient(serverName, sessionId);
|
|
63
81
|
|
|
64
82
|
let result;
|
|
65
83
|
switch (method) {
|
|
@@ -140,10 +158,23 @@ export function createPlaygroundRoutes() {
|
|
|
140
158
|
// Cleanup endpoint to close client connections
|
|
141
159
|
router.cleanup = async (req, res) => {
|
|
142
160
|
const sessionId = req.headers['mcp-session-id'] || req.headers['x-mcp-session-id'];
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
161
|
+
const { serverName } = req.body || {};
|
|
162
|
+
|
|
163
|
+
if (serverName && sessionId) {
|
|
164
|
+
const sessionKey = getSessionKey(serverName, sessionId);
|
|
165
|
+
if (clientSessions.has(sessionKey)) {
|
|
166
|
+
const clientWrapper = clientSessions.get(sessionKey);
|
|
167
|
+
await clientWrapper.close();
|
|
168
|
+
clientSessions.delete(sessionKey);
|
|
169
|
+
}
|
|
170
|
+
} else if (sessionId) {
|
|
171
|
+
// Cleanup all sessions for this sessionId across all servers
|
|
172
|
+
for (const [key, clientWrapper] of clientSessions.entries()) {
|
|
173
|
+
if (key.endsWith(`:${sessionId}`)) {
|
|
174
|
+
await clientWrapper.close();
|
|
175
|
+
clientSessions.delete(key);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
147
178
|
}
|
|
148
179
|
res.json({ success: true });
|
|
149
180
|
};
|