@mcp-shark/mcp-shark 1.4.0 → 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/README.md +16 -1
- package/bin/mcp-shark.js +179 -53
- 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 +4 -2
- 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
package/README.md
CHANGED
|
@@ -66,11 +66,20 @@ Or if installed locally:
|
|
|
66
66
|
npx @mcp-shark/mcp-shark
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
**Open browser automatically:**
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
mcp-shark --open
|
|
73
|
+
# or
|
|
74
|
+
mcp-shark -o
|
|
75
|
+
```
|
|
76
|
+
|
|
69
77
|
The UI will automatically:
|
|
70
78
|
|
|
71
79
|
- Install dependencies if needed
|
|
72
80
|
- Build the frontend if needed
|
|
73
81
|
- Start the server on `http://localhost:9853`
|
|
82
|
+
- Optionally open your browser automatically (with `--open` flag)
|
|
74
83
|
|
|
75
84
|
Open your browser to `http://localhost:9853` to access the MCP Shark interface.
|
|
76
85
|
|
|
@@ -331,8 +340,14 @@ No manual configuration editing required - MCP Shark handles everything for you.
|
|
|
331
340
|
mcp-shark
|
|
332
341
|
```
|
|
333
342
|
|
|
343
|
+
Or to automatically open your browser:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
mcp-shark --open
|
|
347
|
+
```
|
|
348
|
+
|
|
334
349
|
3. **Open your browser:**
|
|
335
|
-
Navigate to `http://localhost:9853`
|
|
350
|
+
Navigate to `http://localhost:9853` (or it will open automatically with `--open` flag)
|
|
336
351
|
|
|
337
352
|
4. **Interactive Tour**: On first launch, you'll see an interactive tour - follow it to get started
|
|
338
353
|
|
package/bin/mcp-shark.js
CHANGED
|
@@ -3,67 +3,110 @@
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { dirname, join, resolve } from 'node:path';
|
|
6
|
-
import { existsSync } from 'node:fs';
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import open from 'open';
|
|
9
|
+
|
|
10
|
+
const SERVER_URL = 'http://localhost:9853';
|
|
11
|
+
const BROWSER_OPEN_DELAY = 1000;
|
|
7
12
|
|
|
8
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
14
|
const __dirname = dirname(__filename);
|
|
10
15
|
const rootDir = resolve(__dirname, '..');
|
|
11
|
-
const uiDir = join(rootDir, 'ui');
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Display welcome banner
|
|
19
|
+
*/
|
|
20
|
+
function displayWelcomeBanner() {
|
|
21
|
+
const pkgPath = join(rootDir, 'package.json');
|
|
22
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
23
|
+
const version = pkg.version;
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const banner = `
|
|
26
|
+
███╗ ███╗ ██████╗ ██████╗ ███████╗██╗ ██╗ █████╗ ██████╗ ██╗ ██╗
|
|
27
|
+
████╗ ████║██╔════╝██╔══██╗ ██╔════╝██║ ██║██╔══██╗██╔══██╗██║ ██╔╝
|
|
28
|
+
██╔████╔██║██║ ██████╔╝ ███████╗███████║███████║██████╔╝█████╔╝
|
|
29
|
+
██║╚██╔╝██║██║ ██╔═══╝ ╚════██║██╔══██║██╔══██║██╔══██╗██╔═██╗
|
|
30
|
+
██║ ╚═╝ ██║╚██████╗██║ ███████║██║ ██║██║ ██║██║ ██║██║ ██╗
|
|
31
|
+
╚═╝ ╚═╝ ╚═════╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝
|
|
32
|
+
|
|
33
|
+
Aggregate multiple MCP servers into a unified interface
|
|
34
|
+
Version: ${version} | Homepage: https://mcpshark.sh
|
|
35
|
+
`;
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
if (code !== 0) {
|
|
31
|
-
console.error('Failed to install dependencies');
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
console.log('Dependencies installed successfully!\n');
|
|
35
|
-
startUIServer();
|
|
36
|
-
});
|
|
37
|
-
} else {
|
|
38
|
-
startUIServer();
|
|
37
|
+
console.log(banner);
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
const uiDir = join(rootDir, 'ui');
|
|
41
|
+
const distDir = join(uiDir, 'dist');
|
|
42
|
+
const rootNodeModules = join(rootDir, 'node_modules');
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Run a command and return a promise that resolves when it completes
|
|
46
|
+
*/
|
|
47
|
+
function runCommand(command, args, options) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const process = spawn(command, args, {
|
|
50
|
+
...options,
|
|
48
51
|
stdio: 'inherit',
|
|
49
52
|
shell: true,
|
|
50
53
|
});
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
process.on('close', (code) => {
|
|
53
56
|
if (code !== 0) {
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
reject(new Error(`Command failed with exit code ${code}`));
|
|
58
|
+
} else {
|
|
59
|
+
resolve();
|
|
56
60
|
}
|
|
57
|
-
runServer();
|
|
58
61
|
});
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
|
|
63
|
+
process.on('error', (error) => {
|
|
64
|
+
reject(error);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Install dependencies in the root directory
|
|
71
|
+
*/
|
|
72
|
+
async function installDependencies() {
|
|
73
|
+
console.log('Installing dependencies...');
|
|
74
|
+
try {
|
|
75
|
+
await runCommand('npm', ['install'], { cwd: rootDir });
|
|
76
|
+
console.log('Dependencies installed successfully!\n');
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('Failed to install dependencies:', error.message);
|
|
79
|
+
process.exit(1);
|
|
61
80
|
}
|
|
62
81
|
}
|
|
63
82
|
|
|
64
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Build the UI for production
|
|
85
|
+
*/
|
|
86
|
+
async function buildUI() {
|
|
87
|
+
console.log('Building UI for production...');
|
|
88
|
+
try {
|
|
89
|
+
await runCommand('npm', ['run', 'build'], { cwd: uiDir });
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('Failed to build UI:', error.message);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Open the browser after a short delay
|
|
98
|
+
*/
|
|
99
|
+
async function openBrowser() {
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, BROWSER_OPEN_DELAY));
|
|
101
|
+
open(SERVER_URL);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Start the UI server
|
|
106
|
+
*/
|
|
107
|
+
async function startServer(shouldOpenBrowser = false) {
|
|
65
108
|
console.log('Starting MCP Shark UI server...');
|
|
66
|
-
console.log(
|
|
109
|
+
console.log(`Open ${SERVER_URL} in your browser`);
|
|
67
110
|
console.log('Press Ctrl+C to stop\n');
|
|
68
111
|
|
|
69
112
|
const serverProcess = spawn('node', ['server.js'], {
|
|
@@ -72,22 +115,105 @@ function runServer() {
|
|
|
72
115
|
shell: true,
|
|
73
116
|
});
|
|
74
117
|
|
|
118
|
+
if (shouldOpenBrowser) {
|
|
119
|
+
await openBrowser();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let isShuttingDown = false;
|
|
123
|
+
|
|
75
124
|
serverProcess.on('close', (code) => {
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
125
|
+
if (!isShuttingDown) {
|
|
126
|
+
if (code !== 0 && code !== null) {
|
|
127
|
+
console.error(`Server exited with code ${code}`);
|
|
128
|
+
process.exit(code);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
process.exit(0);
|
|
79
132
|
}
|
|
80
133
|
});
|
|
81
134
|
|
|
82
135
|
// Handle process termination
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
process.exit(0);
|
|
87
|
-
});
|
|
136
|
+
const shutdown = async (signal) => {
|
|
137
|
+
if (isShuttingDown) return;
|
|
138
|
+
isShuttingDown = true;
|
|
88
139
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
process
|
|
92
|
-
|
|
140
|
+
console.log('Shutting down...');
|
|
141
|
+
|
|
142
|
+
// Send signal to child process
|
|
143
|
+
serverProcess.kill(signal);
|
|
144
|
+
|
|
145
|
+
// Wait for child process to exit, with timeout
|
|
146
|
+
const timeout = setTimeout(() => {
|
|
147
|
+
console.log('Forcefully terminating server process...');
|
|
148
|
+
serverProcess.kill('SIGKILL');
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}, 5000);
|
|
151
|
+
|
|
152
|
+
serverProcess.once('close', () => {
|
|
153
|
+
clearTimeout(timeout);
|
|
154
|
+
process.exit(0);
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
159
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Validate that required directories exist
|
|
164
|
+
*/
|
|
165
|
+
function validateDirectories() {
|
|
166
|
+
if (!existsSync(uiDir)) {
|
|
167
|
+
console.error('Error: UI directory not found. Please ensure you are in the correct directory.');
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Ensure dependencies are installed
|
|
174
|
+
*/
|
|
175
|
+
async function ensureDependencies() {
|
|
176
|
+
if (!existsSync(rootNodeModules)) {
|
|
177
|
+
await installDependencies();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Ensure UI is built
|
|
183
|
+
*/
|
|
184
|
+
async function ensureUIBuilt() {
|
|
185
|
+
if (!existsSync(distDir)) {
|
|
186
|
+
await buildUI();
|
|
187
|
+
}
|
|
93
188
|
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Main execution function
|
|
192
|
+
*/
|
|
193
|
+
async function main() {
|
|
194
|
+
// Display welcome banner
|
|
195
|
+
displayWelcomeBanner();
|
|
196
|
+
|
|
197
|
+
// Parse command line options
|
|
198
|
+
const program = new Command();
|
|
199
|
+
program.option('-o, --open', 'Open the browser', false).parse(process.argv);
|
|
200
|
+
|
|
201
|
+
const options = program.opts();
|
|
202
|
+
|
|
203
|
+
// Validate environment
|
|
204
|
+
validateDirectories();
|
|
205
|
+
|
|
206
|
+
// Ensure dependencies are installed
|
|
207
|
+
await ensureDependencies();
|
|
208
|
+
|
|
209
|
+
// Ensure UI is built
|
|
210
|
+
await ensureUIBuilt();
|
|
211
|
+
|
|
212
|
+
// Start the server
|
|
213
|
+
await startServer(options.open);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
main().catch((error) => {
|
|
217
|
+
console.error('Unexpected error:', error);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
});
|
|
@@ -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
|
}
|