mcp-playbook 0.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/LICENSE +21 -0
- package/README.md +911 -0
- package/bin/mcp-playbook.js +23 -0
- package/bin/postinstall.js +14 -0
- package/dist/cli/index.d.mts +7 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +513 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +498 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/client/assets/index-BtOeKclx.js +40 -0
- package/dist/client/index.html +24 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +8 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server/build.d.mts +26 -0
- package/dist/server/build.d.ts +26 -0
- package/dist/server/build.js +168 -0
- package/dist/server/build.js.map +1 -0
- package/dist/server/build.mjs +132 -0
- package/dist/server/build.mjs.map +1 -0
- package/dist/server/connector.d.mts +119 -0
- package/dist/server/connector.d.ts +119 -0
- package/dist/server/connector.js +142 -0
- package/dist/server/connector.js.map +1 -0
- package/dist/server/connector.mjs +116 -0
- package/dist/server/connector.mjs.map +1 -0
- package/dist/server/dev-server.d.mts +16 -0
- package/dist/server/dev-server.d.ts +16 -0
- package/dist/server/dev-server.js +315 -0
- package/dist/server/dev-server.js.map +1 -0
- package/dist/server/dev-server.mjs +287 -0
- package/dist/server/dev-server.mjs.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server/connector.ts
|
|
21
|
+
var connector_exports = {};
|
|
22
|
+
__export(connector_exports, {
|
|
23
|
+
discoverAllServers: () => discoverAllServers,
|
|
24
|
+
executeTool: () => executeTool
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(connector_exports);
|
|
27
|
+
var import_client = require("@modelcontextprotocol/sdk/client/index.js");
|
|
28
|
+
var import_stdio = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
29
|
+
var import_sse = require("@modelcontextprotocol/sdk/client/sse.js");
|
|
30
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
31
|
+
async function discoverAllServers(servers, extraExamples = {}, tags = {}) {
|
|
32
|
+
const toolTagMap = {};
|
|
33
|
+
for (const [tag, tools] of Object.entries(tags)) {
|
|
34
|
+
for (const tool of tools) {
|
|
35
|
+
if (!toolTagMap[tool]) toolTagMap[tool] = [];
|
|
36
|
+
toolTagMap[tool].push(tag);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const results = await Promise.allSettled(
|
|
40
|
+
servers.map(
|
|
41
|
+
(server, i) => connectToServer(server, i, extraExamples, toolTagMap)
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
return results.map((result, i) => {
|
|
45
|
+
if (result.status === "fulfilled") {
|
|
46
|
+
return result.value;
|
|
47
|
+
} else {
|
|
48
|
+
return {
|
|
49
|
+
id: `server-${i}`,
|
|
50
|
+
name: servers[i].name,
|
|
51
|
+
url: servers[i].url || `${servers[i].command} ${(servers[i].args || []).join(" ")}`,
|
|
52
|
+
status: "error",
|
|
53
|
+
version: "unknown",
|
|
54
|
+
tools: [],
|
|
55
|
+
error: result.reason?.message || "Connection failed"
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async function connectToServer(config, index, extraExamples, toolTagMap) {
|
|
61
|
+
const client = new import_client.Client(
|
|
62
|
+
{ name: "mcp-playbook", version: "0.1.0" },
|
|
63
|
+
{ capabilities: {} }
|
|
64
|
+
);
|
|
65
|
+
let transport;
|
|
66
|
+
if (config.transport === "stdio") {
|
|
67
|
+
if (!config.command) throw new Error(`Server "${config.name}" needs a command for stdio transport`);
|
|
68
|
+
transport = new import_stdio.StdioClientTransport({
|
|
69
|
+
command: config.command,
|
|
70
|
+
args: config.args || [],
|
|
71
|
+
env: config.env || {},
|
|
72
|
+
cwd: config.cwd || process.cwd()
|
|
73
|
+
});
|
|
74
|
+
} else if (config.transport === "http") {
|
|
75
|
+
if (!config.url) throw new Error(`Server "${config.name}" needs a url for http transport`);
|
|
76
|
+
transport = new import_streamableHttp.StreamableHTTPClientTransport(new URL(config.url), {
|
|
77
|
+
requestInit: { headers: config.headers || {} }
|
|
78
|
+
});
|
|
79
|
+
} else if (config.transport === "sse") {
|
|
80
|
+
if (!config.url) throw new Error(`Server "${config.name}" needs a url for sse transport`);
|
|
81
|
+
transport = new import_sse.SSEClientTransport(new URL(config.url), {
|
|
82
|
+
requestInit: { headers: config.headers || {} }
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
throw new Error(`Unknown transport: ${config.transport}`);
|
|
86
|
+
}
|
|
87
|
+
await client.connect(transport);
|
|
88
|
+
const { tools } = await client.listTools();
|
|
89
|
+
const serverInfo = client.getServerVersion();
|
|
90
|
+
const discoveredTools = tools.map((tool) => ({
|
|
91
|
+
name: tool.name,
|
|
92
|
+
description: tool.description || "",
|
|
93
|
+
inputSchema: tool.inputSchema,
|
|
94
|
+
examples: extraExamples[tool.name] || [],
|
|
95
|
+
tags: toolTagMap[tool.name] || []
|
|
96
|
+
}));
|
|
97
|
+
return {
|
|
98
|
+
id: `server-${index}`,
|
|
99
|
+
name: config.name,
|
|
100
|
+
url: config.url || `${config.command} ${(config.args || []).join(" ")}`,
|
|
101
|
+
status: "connected",
|
|
102
|
+
version: serverInfo?.version || "1.0.0",
|
|
103
|
+
tools: discoveredTools
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function executeTool(serverConfig, toolName, input) {
|
|
107
|
+
const start = Date.now();
|
|
108
|
+
try {
|
|
109
|
+
const client = new import_client.Client(
|
|
110
|
+
{ name: "mcp-playbook", version: "0.1.0" },
|
|
111
|
+
{ capabilities: {} }
|
|
112
|
+
);
|
|
113
|
+
let transport;
|
|
114
|
+
if (serverConfig.transport === "stdio") {
|
|
115
|
+
transport = new import_stdio.StdioClientTransport({
|
|
116
|
+
command: serverConfig.command,
|
|
117
|
+
args: serverConfig.args || []
|
|
118
|
+
});
|
|
119
|
+
} else {
|
|
120
|
+
transport = new import_streamableHttp.StreamableHTTPClientTransport(new URL(serverConfig.url));
|
|
121
|
+
}
|
|
122
|
+
await client.connect(transport);
|
|
123
|
+
const result = await client.callTool({ name: toolName, arguments: input });
|
|
124
|
+
await client.close();
|
|
125
|
+
return {
|
|
126
|
+
result,
|
|
127
|
+
duration_ms: Date.now() - start
|
|
128
|
+
};
|
|
129
|
+
} catch (err) {
|
|
130
|
+
return {
|
|
131
|
+
result: null,
|
|
132
|
+
duration_ms: Date.now() - start,
|
|
133
|
+
error: err.message
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
138
|
+
0 && (module.exports = {
|
|
139
|
+
discoverAllServers,
|
|
140
|
+
executeTool
|
|
141
|
+
});
|
|
142
|
+
//# sourceMappingURL=connector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/connector.ts"],"sourcesContent":["/**\n * src/server/connector.ts\n *\n * Connects to MCP servers and discovers their tools.\n * This is the bridge between the user's MCP servers and\n * the Playbook UI.\n *\n * Supports:\n * - stdio transport (spawn a process, talk over stdin/stdout)\n * - HTTP / SSE transport (connect to a running server)\n *\n * Returns a unified DiscoveredServer[] with all tool schemas.\n */\n\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'\nimport { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport type { ServerConfig, ToolExample } from '../config.js'\n\nexport interface DiscoveredTool {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n examples: ToolExample[]\n tags: string[]\n}\n\nexport interface DiscoveredServer {\n id: string\n name: string\n url: string\n status: 'connected' | 'disconnected' | 'error'\n version: string\n tools: DiscoveredTool[]\n error?: string\n}\n\n/**\n * Connect to all configured servers and discover their tools.\n * Runs all connections in parallel — slow servers don't block fast ones.\n */\nexport async function discoverAllServers(\n servers: ServerConfig[],\n extraExamples: Record<string, ToolExample[]> = {},\n tags: Record<string, string[]> = {}\n): Promise<DiscoveredServer[]> {\n\n // Build a reverse lookup: tool name → tag names\n const toolTagMap: Record<string, string[]> = {}\n for (const [tag, tools] of Object.entries(tags)) {\n for (const tool of tools) {\n if (!toolTagMap[tool]) toolTagMap[tool] = []\n toolTagMap[tool].push(tag)\n }\n }\n\n // Connect to all servers in parallel\n const results = await Promise.allSettled(\n servers.map((server, i) =>\n connectToServer(server, i, extraExamples, toolTagMap)\n )\n )\n\n return results.map((result, i) => {\n if (result.status === 'fulfilled') {\n return result.value\n } else {\n return {\n id: `server-${i}`,\n name: servers[i].name,\n url: servers[i].url || `${servers[i].command} ${(servers[i].args || []).join(' ')}`,\n status: 'error' as const,\n version: 'unknown',\n tools: [],\n error: result.reason?.message || 'Connection failed'\n }\n }\n })\n}\n\nasync function connectToServer(\n config: ServerConfig,\n index: number,\n extraExamples: Record<string, ToolExample[]>,\n toolTagMap: Record<string, string[]>\n): Promise<DiscoveredServer> {\n\n const client = new Client(\n { name: 'mcp-playbook', version: '0.1.0' },\n { capabilities: {} }\n )\n\n // Create the right transport based on config\n let transport\n\n if (config.transport === 'stdio') {\n if (!config.command) throw new Error(`Server \"${config.name}\" needs a command for stdio transport`)\n transport = new StdioClientTransport({\n command: config.command,\n args: config.args || [],\n env: config.env || {},\n cwd: config.cwd || process.cwd()\n })\n } else if (config.transport === 'http') {\n if (!config.url) throw new Error(`Server \"${config.name}\" needs a url for http transport`)\n transport = new StreamableHTTPClientTransport(new URL(config.url), {\n requestInit: { headers: config.headers || {} }\n })\n } else if (config.transport === 'sse') {\n if (!config.url) throw new Error(`Server \"${config.name}\" needs a url for sse transport`)\n transport = new SSEClientTransport(new URL(config.url), {\n requestInit: { headers: config.headers || {} }\n })\n } else {\n throw new Error(`Unknown transport: ${(config as any).transport}`)\n }\n\n await client.connect(transport)\n\n // Fetch tools from the server\n const { tools } = await client.listTools()\n\n // Fetch server info if available\n const serverInfo = client.getServerVersion()\n\n // Map tools to our format\n const discoveredTools: DiscoveredTool[] = tools.map(tool => ({\n name: tool.name,\n description: tool.description || '',\n inputSchema: tool.inputSchema as Record<string, unknown>,\n examples: extraExamples[tool.name] || [],\n tags: toolTagMap[tool.name] || []\n }))\n\n return {\n id: `server-${index}`,\n name: config.name,\n url: config.url || `${config.command} ${(config.args || []).join(' ')}`,\n status: 'connected',\n version: serverInfo?.version || '1.0.0',\n tools: discoveredTools\n }\n}\n\n/**\n * Execute a tool call against a specific server.\n * Used by the Try tab in the UI.\n */\nexport async function executeTool(\n serverConfig: ServerConfig,\n toolName: string,\n input: Record<string, unknown>\n): Promise<{ result: unknown; duration_ms: number; error?: string }> {\n\n const start = Date.now()\n\n try {\n const client = new Client(\n { name: 'mcp-playbook', version: '0.1.0' },\n { capabilities: {} }\n )\n\n let transport\n if (serverConfig.transport === 'stdio') {\n transport = new StdioClientTransport({\n command: serverConfig.command!,\n args: serverConfig.args || [],\n })\n } else {\n transport = new StreamableHTTPClientTransport(new URL(serverConfig.url!))\n }\n\n await client.connect(transport)\n\n const result = await client.callTool({ name: toolName, arguments: input })\n await client.close()\n\n return {\n result,\n duration_ms: Date.now() - start\n }\n } catch (err: any) {\n return {\n result: null,\n duration_ms: Date.now() - start,\n error: err.message\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/server/connector.ts
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
5
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
6
|
+
async function discoverAllServers(servers, extraExamples = {}, tags = {}) {
|
|
7
|
+
const toolTagMap = {};
|
|
8
|
+
for (const [tag, tools] of Object.entries(tags)) {
|
|
9
|
+
for (const tool of tools) {
|
|
10
|
+
if (!toolTagMap[tool]) toolTagMap[tool] = [];
|
|
11
|
+
toolTagMap[tool].push(tag);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const results = await Promise.allSettled(
|
|
15
|
+
servers.map(
|
|
16
|
+
(server, i) => connectToServer(server, i, extraExamples, toolTagMap)
|
|
17
|
+
)
|
|
18
|
+
);
|
|
19
|
+
return results.map((result, i) => {
|
|
20
|
+
if (result.status === "fulfilled") {
|
|
21
|
+
return result.value;
|
|
22
|
+
} else {
|
|
23
|
+
return {
|
|
24
|
+
id: `server-${i}`,
|
|
25
|
+
name: servers[i].name,
|
|
26
|
+
url: servers[i].url || `${servers[i].command} ${(servers[i].args || []).join(" ")}`,
|
|
27
|
+
status: "error",
|
|
28
|
+
version: "unknown",
|
|
29
|
+
tools: [],
|
|
30
|
+
error: result.reason?.message || "Connection failed"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async function connectToServer(config, index, extraExamples, toolTagMap) {
|
|
36
|
+
const client = new Client(
|
|
37
|
+
{ name: "mcp-playbook", version: "0.1.0" },
|
|
38
|
+
{ capabilities: {} }
|
|
39
|
+
);
|
|
40
|
+
let transport;
|
|
41
|
+
if (config.transport === "stdio") {
|
|
42
|
+
if (!config.command) throw new Error(`Server "${config.name}" needs a command for stdio transport`);
|
|
43
|
+
transport = new StdioClientTransport({
|
|
44
|
+
command: config.command,
|
|
45
|
+
args: config.args || [],
|
|
46
|
+
env: config.env || {},
|
|
47
|
+
cwd: config.cwd || process.cwd()
|
|
48
|
+
});
|
|
49
|
+
} else if (config.transport === "http") {
|
|
50
|
+
if (!config.url) throw new Error(`Server "${config.name}" needs a url for http transport`);
|
|
51
|
+
transport = new StreamableHTTPClientTransport(new URL(config.url), {
|
|
52
|
+
requestInit: { headers: config.headers || {} }
|
|
53
|
+
});
|
|
54
|
+
} else if (config.transport === "sse") {
|
|
55
|
+
if (!config.url) throw new Error(`Server "${config.name}" needs a url for sse transport`);
|
|
56
|
+
transport = new SSEClientTransport(new URL(config.url), {
|
|
57
|
+
requestInit: { headers: config.headers || {} }
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
throw new Error(`Unknown transport: ${config.transport}`);
|
|
61
|
+
}
|
|
62
|
+
await client.connect(transport);
|
|
63
|
+
const { tools } = await client.listTools();
|
|
64
|
+
const serverInfo = client.getServerVersion();
|
|
65
|
+
const discoveredTools = tools.map((tool) => ({
|
|
66
|
+
name: tool.name,
|
|
67
|
+
description: tool.description || "",
|
|
68
|
+
inputSchema: tool.inputSchema,
|
|
69
|
+
examples: extraExamples[tool.name] || [],
|
|
70
|
+
tags: toolTagMap[tool.name] || []
|
|
71
|
+
}));
|
|
72
|
+
return {
|
|
73
|
+
id: `server-${index}`,
|
|
74
|
+
name: config.name,
|
|
75
|
+
url: config.url || `${config.command} ${(config.args || []).join(" ")}`,
|
|
76
|
+
status: "connected",
|
|
77
|
+
version: serverInfo?.version || "1.0.0",
|
|
78
|
+
tools: discoveredTools
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async function executeTool(serverConfig, toolName, input) {
|
|
82
|
+
const start = Date.now();
|
|
83
|
+
try {
|
|
84
|
+
const client = new Client(
|
|
85
|
+
{ name: "mcp-playbook", version: "0.1.0" },
|
|
86
|
+
{ capabilities: {} }
|
|
87
|
+
);
|
|
88
|
+
let transport;
|
|
89
|
+
if (serverConfig.transport === "stdio") {
|
|
90
|
+
transport = new StdioClientTransport({
|
|
91
|
+
command: serverConfig.command,
|
|
92
|
+
args: serverConfig.args || []
|
|
93
|
+
});
|
|
94
|
+
} else {
|
|
95
|
+
transport = new StreamableHTTPClientTransport(new URL(serverConfig.url));
|
|
96
|
+
}
|
|
97
|
+
await client.connect(transport);
|
|
98
|
+
const result = await client.callTool({ name: toolName, arguments: input });
|
|
99
|
+
await client.close();
|
|
100
|
+
return {
|
|
101
|
+
result,
|
|
102
|
+
duration_ms: Date.now() - start
|
|
103
|
+
};
|
|
104
|
+
} catch (err) {
|
|
105
|
+
return {
|
|
106
|
+
result: null,
|
|
107
|
+
duration_ms: Date.now() - start,
|
|
108
|
+
error: err.message
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
discoverAllServers,
|
|
114
|
+
executeTool
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=connector.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/connector.ts"],"sourcesContent":["/**\n * src/server/connector.ts\n *\n * Connects to MCP servers and discovers their tools.\n * This is the bridge between the user's MCP servers and\n * the Playbook UI.\n *\n * Supports:\n * - stdio transport (spawn a process, talk over stdin/stdout)\n * - HTTP / SSE transport (connect to a running server)\n *\n * Returns a unified DiscoveredServer[] with all tool schemas.\n */\n\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'\nimport { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport type { ServerConfig, ToolExample } from '../config.js'\n\nexport interface DiscoveredTool {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n examples: ToolExample[]\n tags: string[]\n}\n\nexport interface DiscoveredServer {\n id: string\n name: string\n url: string\n status: 'connected' | 'disconnected' | 'error'\n version: string\n tools: DiscoveredTool[]\n error?: string\n}\n\n/**\n * Connect to all configured servers and discover their tools.\n * Runs all connections in parallel — slow servers don't block fast ones.\n */\nexport async function discoverAllServers(\n servers: ServerConfig[],\n extraExamples: Record<string, ToolExample[]> = {},\n tags: Record<string, string[]> = {}\n): Promise<DiscoveredServer[]> {\n\n // Build a reverse lookup: tool name → tag names\n const toolTagMap: Record<string, string[]> = {}\n for (const [tag, tools] of Object.entries(tags)) {\n for (const tool of tools) {\n if (!toolTagMap[tool]) toolTagMap[tool] = []\n toolTagMap[tool].push(tag)\n }\n }\n\n // Connect to all servers in parallel\n const results = await Promise.allSettled(\n servers.map((server, i) =>\n connectToServer(server, i, extraExamples, toolTagMap)\n )\n )\n\n return results.map((result, i) => {\n if (result.status === 'fulfilled') {\n return result.value\n } else {\n return {\n id: `server-${i}`,\n name: servers[i].name,\n url: servers[i].url || `${servers[i].command} ${(servers[i].args || []).join(' ')}`,\n status: 'error' as const,\n version: 'unknown',\n tools: [],\n error: result.reason?.message || 'Connection failed'\n }\n }\n })\n}\n\nasync function connectToServer(\n config: ServerConfig,\n index: number,\n extraExamples: Record<string, ToolExample[]>,\n toolTagMap: Record<string, string[]>\n): Promise<DiscoveredServer> {\n\n const client = new Client(\n { name: 'mcp-playbook', version: '0.1.0' },\n { capabilities: {} }\n )\n\n // Create the right transport based on config\n let transport\n\n if (config.transport === 'stdio') {\n if (!config.command) throw new Error(`Server \"${config.name}\" needs a command for stdio transport`)\n transport = new StdioClientTransport({\n command: config.command,\n args: config.args || [],\n env: config.env || {},\n cwd: config.cwd || process.cwd()\n })\n } else if (config.transport === 'http') {\n if (!config.url) throw new Error(`Server \"${config.name}\" needs a url for http transport`)\n transport = new StreamableHTTPClientTransport(new URL(config.url), {\n requestInit: { headers: config.headers || {} }\n })\n } else if (config.transport === 'sse') {\n if (!config.url) throw new Error(`Server \"${config.name}\" needs a url for sse transport`)\n transport = new SSEClientTransport(new URL(config.url), {\n requestInit: { headers: config.headers || {} }\n })\n } else {\n throw new Error(`Unknown transport: ${(config as any).transport}`)\n }\n\n await client.connect(transport)\n\n // Fetch tools from the server\n const { tools } = await client.listTools()\n\n // Fetch server info if available\n const serverInfo = client.getServerVersion()\n\n // Map tools to our format\n const discoveredTools: DiscoveredTool[] = tools.map(tool => ({\n name: tool.name,\n description: tool.description || '',\n inputSchema: tool.inputSchema as Record<string, unknown>,\n examples: extraExamples[tool.name] || [],\n tags: toolTagMap[tool.name] || []\n }))\n\n return {\n id: `server-${index}`,\n name: config.name,\n url: config.url || `${config.command} ${(config.args || []).join(' ')}`,\n status: 'connected',\n version: serverInfo?.version || '1.0.0',\n tools: discoveredTools\n }\n}\n\n/**\n * Execute a tool call against a specific server.\n * Used by the Try tab in the UI.\n */\nexport async function executeTool(\n serverConfig: ServerConfig,\n toolName: string,\n input: Record<string, unknown>\n): Promise<{ result: unknown; duration_ms: number; error?: string }> {\n\n const start = Date.now()\n\n try {\n const client = new Client(\n { name: 'mcp-playbook', version: '0.1.0' },\n { capabilities: {} }\n )\n\n let transport\n if (serverConfig.transport === 'stdio') {\n transport = new StdioClientTransport({\n command: serverConfig.command!,\n args: serverConfig.args || [],\n })\n } else {\n transport = new StreamableHTTPClientTransport(new URL(serverConfig.url!))\n }\n\n await client.connect(transport)\n\n const result = await client.callTool({ name: toolName, arguments: input })\n await client.close()\n\n return {\n result,\n duration_ms: Date.now() - start\n }\n } catch (err: any) {\n return {\n result: null,\n duration_ms: Date.now() - start,\n error: err.message\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* src/server/dev-server.ts
|
|
5
|
+
*
|
|
6
|
+
* The development server.
|
|
7
|
+
* Serves the React UI + REST API + WebSocket hot reload.
|
|
8
|
+
*/
|
|
9
|
+
interface DevServerOptions {
|
|
10
|
+
port: number;
|
|
11
|
+
configPath: string;
|
|
12
|
+
open: boolean;
|
|
13
|
+
}
|
|
14
|
+
declare function startDevServer(options: DevServerOptions): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
|
|
15
|
+
|
|
16
|
+
export { startDevServer };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* src/server/dev-server.ts
|
|
5
|
+
*
|
|
6
|
+
* The development server.
|
|
7
|
+
* Serves the React UI + REST API + WebSocket hot reload.
|
|
8
|
+
*/
|
|
9
|
+
interface DevServerOptions {
|
|
10
|
+
port: number;
|
|
11
|
+
configPath: string;
|
|
12
|
+
open: boolean;
|
|
13
|
+
}
|
|
14
|
+
declare function startDevServer(options: DevServerOptions): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
|
|
15
|
+
|
|
16
|
+
export { startDevServer };
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/server/dev-server.ts
|
|
31
|
+
var dev_server_exports = {};
|
|
32
|
+
__export(dev_server_exports, {
|
|
33
|
+
startDevServer: () => startDevServer
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(dev_server_exports);
|
|
36
|
+
var import_express = __toESM(require("express"));
|
|
37
|
+
var import_http = require("http");
|
|
38
|
+
var import_ws = require("ws");
|
|
39
|
+
var import_path = __toESM(require("path"));
|
|
40
|
+
var import_fs = __toESM(require("fs"));
|
|
41
|
+
var import_chalk = __toESM(require("chalk"));
|
|
42
|
+
var import_chokidar = __toESM(require("chokidar"));
|
|
43
|
+
var import_open = __toESM(require("open"));
|
|
44
|
+
|
|
45
|
+
// src/server/connector.ts
|
|
46
|
+
var import_client = require("@modelcontextprotocol/sdk/client/index.js");
|
|
47
|
+
var import_stdio = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
48
|
+
var import_sse = require("@modelcontextprotocol/sdk/client/sse.js");
|
|
49
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
50
|
+
async function discoverAllServers(servers, extraExamples = {}, tags = {}) {
|
|
51
|
+
const toolTagMap = {};
|
|
52
|
+
for (const [tag, tools] of Object.entries(tags)) {
|
|
53
|
+
for (const tool of tools) {
|
|
54
|
+
if (!toolTagMap[tool]) toolTagMap[tool] = [];
|
|
55
|
+
toolTagMap[tool].push(tag);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const results = await Promise.allSettled(
|
|
59
|
+
servers.map(
|
|
60
|
+
(server, i) => connectToServer(server, i, extraExamples, toolTagMap)
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
return results.map((result, i) => {
|
|
64
|
+
if (result.status === "fulfilled") {
|
|
65
|
+
return result.value;
|
|
66
|
+
} else {
|
|
67
|
+
return {
|
|
68
|
+
id: `server-${i}`,
|
|
69
|
+
name: servers[i].name,
|
|
70
|
+
url: servers[i].url || `${servers[i].command} ${(servers[i].args || []).join(" ")}`,
|
|
71
|
+
status: "error",
|
|
72
|
+
version: "unknown",
|
|
73
|
+
tools: [],
|
|
74
|
+
error: result.reason?.message || "Connection failed"
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async function connectToServer(config, index, extraExamples, toolTagMap) {
|
|
80
|
+
const client = new import_client.Client(
|
|
81
|
+
{ name: "mcp-playbook", version: "0.1.0" },
|
|
82
|
+
{ capabilities: {} }
|
|
83
|
+
);
|
|
84
|
+
let transport;
|
|
85
|
+
if (config.transport === "stdio") {
|
|
86
|
+
if (!config.command) throw new Error(`Server "${config.name}" needs a command for stdio transport`);
|
|
87
|
+
transport = new import_stdio.StdioClientTransport({
|
|
88
|
+
command: config.command,
|
|
89
|
+
args: config.args || [],
|
|
90
|
+
env: config.env || {},
|
|
91
|
+
cwd: config.cwd || process.cwd()
|
|
92
|
+
});
|
|
93
|
+
} else if (config.transport === "http") {
|
|
94
|
+
if (!config.url) throw new Error(`Server "${config.name}" needs a url for http transport`);
|
|
95
|
+
transport = new import_streamableHttp.StreamableHTTPClientTransport(new URL(config.url), {
|
|
96
|
+
requestInit: { headers: config.headers || {} }
|
|
97
|
+
});
|
|
98
|
+
} else if (config.transport === "sse") {
|
|
99
|
+
if (!config.url) throw new Error(`Server "${config.name}" needs a url for sse transport`);
|
|
100
|
+
transport = new import_sse.SSEClientTransport(new URL(config.url), {
|
|
101
|
+
requestInit: { headers: config.headers || {} }
|
|
102
|
+
});
|
|
103
|
+
} else {
|
|
104
|
+
throw new Error(`Unknown transport: ${config.transport}`);
|
|
105
|
+
}
|
|
106
|
+
await client.connect(transport);
|
|
107
|
+
const { tools } = await client.listTools();
|
|
108
|
+
const serverInfo = client.getServerVersion();
|
|
109
|
+
const discoveredTools = tools.map((tool) => ({
|
|
110
|
+
name: tool.name,
|
|
111
|
+
description: tool.description || "",
|
|
112
|
+
inputSchema: tool.inputSchema,
|
|
113
|
+
examples: extraExamples[tool.name] || [],
|
|
114
|
+
tags: toolTagMap[tool.name] || []
|
|
115
|
+
}));
|
|
116
|
+
return {
|
|
117
|
+
id: `server-${index}`,
|
|
118
|
+
name: config.name,
|
|
119
|
+
url: config.url || `${config.command} ${(config.args || []).join(" ")}`,
|
|
120
|
+
status: "connected",
|
|
121
|
+
version: serverInfo?.version || "1.0.0",
|
|
122
|
+
tools: discoveredTools
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function executeTool(serverConfig, toolName, input) {
|
|
126
|
+
const start = Date.now();
|
|
127
|
+
try {
|
|
128
|
+
const client = new import_client.Client(
|
|
129
|
+
{ name: "mcp-playbook", version: "0.1.0" },
|
|
130
|
+
{ capabilities: {} }
|
|
131
|
+
);
|
|
132
|
+
let transport;
|
|
133
|
+
if (serverConfig.transport === "stdio") {
|
|
134
|
+
transport = new import_stdio.StdioClientTransport({
|
|
135
|
+
command: serverConfig.command,
|
|
136
|
+
args: serverConfig.args || []
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
transport = new import_streamableHttp.StreamableHTTPClientTransport(new URL(serverConfig.url));
|
|
140
|
+
}
|
|
141
|
+
await client.connect(transport);
|
|
142
|
+
const result = await client.callTool({ name: toolName, arguments: input });
|
|
143
|
+
await client.close();
|
|
144
|
+
return {
|
|
145
|
+
result,
|
|
146
|
+
duration_ms: Date.now() - start
|
|
147
|
+
};
|
|
148
|
+
} catch (err) {
|
|
149
|
+
return {
|
|
150
|
+
result: null,
|
|
151
|
+
duration_ms: Date.now() - start,
|
|
152
|
+
error: err.message
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/server/dev-server.ts
|
|
158
|
+
function resolveClientDist() {
|
|
159
|
+
const fromCompiled = import_path.default.join(__dirname, "../client");
|
|
160
|
+
if (import_fs.default.existsSync(fromCompiled)) return fromCompiled;
|
|
161
|
+
const fromRoot = import_path.default.join(__dirname, "../../dist/client");
|
|
162
|
+
if (import_fs.default.existsSync(fromRoot)) return fromRoot;
|
|
163
|
+
return fromCompiled;
|
|
164
|
+
}
|
|
165
|
+
function fallbackHTML(port) {
|
|
166
|
+
return `<!DOCTYPE html>
|
|
167
|
+
<html>
|
|
168
|
+
<head>
|
|
169
|
+
<meta charset="UTF-8"/>
|
|
170
|
+
<title>MCP Playbook</title>
|
|
171
|
+
<style>
|
|
172
|
+
body { font-family: monospace; background: #080808; color: #e2e0d8;
|
|
173
|
+
display: flex; align-items: center; justify-content: center;
|
|
174
|
+
height: 100vh; margin: 0; flex-direction: column; gap: 12px; }
|
|
175
|
+
code { background: #1a1a2e; color: #a39cf0; padding: 4px 10px;
|
|
176
|
+
border-radius: 4px; font-size: 14px; }
|
|
177
|
+
p { color: #5a5856; font-size: 13px; }
|
|
178
|
+
</style>
|
|
179
|
+
</head>
|
|
180
|
+
<body>
|
|
181
|
+
<div style="font-size:32px">\u26A1</div>
|
|
182
|
+
<h2 style="margin:0">MCP Playbook API is running</h2>
|
|
183
|
+
<p>The React UI bundle was not found at <code>dist/client/</code></p>
|
|
184
|
+
<p>Run <code>npm run build:client</code> inside the mcp-playbook library to build it.</p>
|
|
185
|
+
<p>API available at <code>http://localhost:${port}/api/servers</code></p>
|
|
186
|
+
</body>
|
|
187
|
+
</html>`;
|
|
188
|
+
}
|
|
189
|
+
async function startDevServer(options) {
|
|
190
|
+
const app = (0, import_express.default)();
|
|
191
|
+
const server = (0, import_http.createServer)(app);
|
|
192
|
+
const wss = new import_ws.WebSocketServer({ server });
|
|
193
|
+
app.use(import_express.default.json());
|
|
194
|
+
const clientDist = resolveClientDist();
|
|
195
|
+
const hasClient = import_fs.default.existsSync(import_path.default.join(clientDist, "index.html"));
|
|
196
|
+
if (hasClient) {
|
|
197
|
+
app.use(import_express.default.static(clientDist));
|
|
198
|
+
console.log(import_chalk.default.dim(` Client: serving from ${clientDist}`));
|
|
199
|
+
} else {
|
|
200
|
+
console.log(import_chalk.default.yellow(` Warning: client bundle not found at ${clientDist}`));
|
|
201
|
+
console.log(import_chalk.default.dim(" API is available but UI will show a placeholder"));
|
|
202
|
+
}
|
|
203
|
+
async function loadConfig() {
|
|
204
|
+
const key = require.resolve(options.configPath);
|
|
205
|
+
delete require.cache[key];
|
|
206
|
+
try {
|
|
207
|
+
return require(options.configPath).default;
|
|
208
|
+
} catch {
|
|
209
|
+
const jsPath = options.configPath.replace(/\.ts$/, ".js");
|
|
210
|
+
if (import_fs.default.existsSync(jsPath)) {
|
|
211
|
+
delete require.cache[require.resolve(jsPath)];
|
|
212
|
+
return require(jsPath).default;
|
|
213
|
+
}
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Cannot load config: ${options.configPath}
|
|
216
|
+
Make sure ts-node is installed: npm install -D ts-node
|
|
217
|
+
Or compile your config to JS first.`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
let cachedServers = [];
|
|
222
|
+
let cachedConfig = null;
|
|
223
|
+
async function refreshServers() {
|
|
224
|
+
try {
|
|
225
|
+
const config = await loadConfig();
|
|
226
|
+
cachedConfig = config;
|
|
227
|
+
cachedServers = await discoverAllServers(
|
|
228
|
+
config.servers,
|
|
229
|
+
config.examples || {},
|
|
230
|
+
config.tags || {}
|
|
231
|
+
);
|
|
232
|
+
const connected = cachedServers.filter((s) => s.status === "connected").length;
|
|
233
|
+
const tools = cachedServers.reduce((acc, s) => acc + s.tools.length, 0);
|
|
234
|
+
console.log(import_chalk.default.green(` \u2713 ${connected}/${cachedServers.length} servers \xB7 ${tools} tools`));
|
|
235
|
+
broadcastToClients({ type: "reload" });
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.error(import_chalk.default.red(` \u2717 ${err.message}`));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function broadcastToClients(data) {
|
|
241
|
+
const msg = JSON.stringify(data);
|
|
242
|
+
wss.clients.forEach((c) => {
|
|
243
|
+
if (c.readyState === 1) c.send(msg);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
app.get("/api/servers", async (_req, res) => {
|
|
247
|
+
try {
|
|
248
|
+
res.json({
|
|
249
|
+
title: cachedConfig?.title || "MCP Playbook",
|
|
250
|
+
description: cachedConfig?.description || "",
|
|
251
|
+
theme: cachedConfig?.theme || {},
|
|
252
|
+
servers: cachedServers
|
|
253
|
+
});
|
|
254
|
+
} catch (err) {
|
|
255
|
+
res.status(500).json({ error: err.message });
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
app.post("/api/refresh", async (_req, res) => {
|
|
259
|
+
await refreshServers();
|
|
260
|
+
res.json({ ok: true });
|
|
261
|
+
});
|
|
262
|
+
app.post("/api/execute", async (req, res) => {
|
|
263
|
+
const { serverId, toolName, input } = req.body;
|
|
264
|
+
if (!cachedConfig) return res.status(503).json({ error: "Config not loaded" });
|
|
265
|
+
const idx = parseInt(serverId.replace("server-", ""));
|
|
266
|
+
const serverConfig = cachedConfig.servers[idx];
|
|
267
|
+
if (!serverConfig) return res.status(404).json({ error: `Server not found: ${serverId}` });
|
|
268
|
+
const result = await executeTool(serverConfig, toolName, input || {});
|
|
269
|
+
res.json(result);
|
|
270
|
+
});
|
|
271
|
+
app.get("*", (_req, res) => {
|
|
272
|
+
const indexPath = import_path.default.join(clientDist, "index.html");
|
|
273
|
+
if (import_fs.default.existsSync(indexPath)) {
|
|
274
|
+
res.sendFile(indexPath);
|
|
275
|
+
} else {
|
|
276
|
+
res.send(fallbackHTML(options.port));
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
wss.on("connection", (ws) => {
|
|
280
|
+
ws.send(JSON.stringify({ type: "connected" }));
|
|
281
|
+
});
|
|
282
|
+
const watcher = import_chokidar.default.watch(options.configPath, { ignoreInitial: true });
|
|
283
|
+
watcher.on("change", () => {
|
|
284
|
+
console.log(import_chalk.default.dim("\n Config changed \u2014 refreshing..."));
|
|
285
|
+
refreshServers();
|
|
286
|
+
});
|
|
287
|
+
await new Promise((resolve, reject) => {
|
|
288
|
+
server.on("error", (err) => {
|
|
289
|
+
if (err.code === "EADDRINUSE") {
|
|
290
|
+
reject(new Error(`Port ${options.port} is already in use. Try: --port ${options.port + 1}`));
|
|
291
|
+
} else {
|
|
292
|
+
reject(err);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
server.listen(options.port, () => resolve());
|
|
296
|
+
});
|
|
297
|
+
await refreshServers();
|
|
298
|
+
const url = `http://localhost:${options.port}`;
|
|
299
|
+
console.log(import_chalk.default.dim(`
|
|
300
|
+
Local: `) + import_chalk.default.cyan(url));
|
|
301
|
+
console.log(import_chalk.default.dim(` Config: ${options.configPath}
|
|
302
|
+
`));
|
|
303
|
+
if (options.open && hasClient) {
|
|
304
|
+
try {
|
|
305
|
+
await (0, import_open.default)(url);
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return server;
|
|
310
|
+
}
|
|
311
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
312
|
+
0 && (module.exports = {
|
|
313
|
+
startDevServer
|
|
314
|
+
});
|
|
315
|
+
//# sourceMappingURL=dev-server.js.map
|