mcpico 0.1.0 → 0.1.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 CHANGED
@@ -24,7 +24,8 @@ Group related tools under a single entry point. The model sees 9 groups instead
24
24
  - **Tool bundling** — Groups tools by prefix (configurable separator), collapsing flat tool lists
25
25
  - **Auto-generated help** — Each group's `help` subcommand is built from upstream tool schemas
26
26
  - **Multi-server aggregation** — Proxy multiple upstream MCP servers through one interface
27
- - **Dual transport** — Supports both stdio and Streamable HTTP (SSE) upstream servers
27
+ - **Dual upstream transport** — Supports both stdio and Streamable HTTP (SSE) upstream servers
28
+ - **Dual listen transport** — MCPico itself listens via stdio or HTTP/SSE (configurable port)
28
29
  - **Configurable timeouts** — Per-server connection timeout with sensible default (30s)
29
30
  - **Resource & prompt passthrough** — Namespaced to avoid collisions across servers
30
31
 
@@ -132,6 +133,25 @@ Groups from different servers are merged if they share a prefix. Otherwise each
132
133
  | `servers` | `ServerConfig[]` | **required** | Upstream MCP servers to proxy |
133
134
  | `separator` | `string` | `"_"` | Separator for prefix-based tool grouping |
134
135
  | `groups` | `object` | `{}` | Explicit group overrides (`{ "group": ["tool1","tool2"] }`) |
136
+ | `listen` | `ListenConfig` | `{"type":"stdio"}` | How MCPico exposes itself to MCP clients |
137
+
138
+ ### ListenConfig
139
+
140
+ | Field | Type | Required | Description |
141
+ |-------|------|----------|-------------|
142
+ | `type` | `"stdio"` | yes | Standard stdio transport |
143
+ | `type` | `"sse"` | yes | HTTP/SSE — specify `port` and optional `host` |
144
+
145
+ ```json
146
+ // SSE listen mode — MCPico as an HTTP endpoint
147
+ {
148
+ "servers": [...],
149
+ "listen": {
150
+ "type": "sse",
151
+ "port": 3000
152
+ }
153
+ }
154
+ ```
135
155
 
136
156
  ### ServerConfig
137
157
 
@@ -163,7 +183,7 @@ Groups from different servers are merged if they share a prefix. Otherwise each
163
183
  ```bash
164
184
  npm install
165
185
  npm run build # TypeScript compilation
166
- npm test # Run tests (77 tests, vitest)
186
+ npm test # Run tests (105 tests, vitest)
167
187
  npm run dev # Run directly with tsx
168
188
  ```
169
189
 
package/dist/config.d.ts CHANGED
@@ -29,6 +29,16 @@ export interface ServerConfig {
29
29
  export interface GroupOverrides {
30
30
  [groupName: string]: string[];
31
31
  }
32
+ /**
33
+ * Transport to expose MCPico itself to clients.
34
+ */
35
+ export type ListenConfig = {
36
+ type: "stdio";
37
+ } | {
38
+ type: "sse";
39
+ port: number;
40
+ host?: string;
41
+ };
32
42
  /**
33
43
  * Full MCPico configuration.
34
44
  */
@@ -39,6 +49,8 @@ export interface MCPicoConfig {
39
49
  separator?: string;
40
50
  /** Explicit group overrides — tools not listed here are auto-grouped */
41
51
  groups?: GroupOverrides;
52
+ /** How MCPico exposes itself to clients (default: stdio) */
53
+ listen?: ListenConfig;
42
54
  }
43
55
  /**
44
56
  * Validate server config and return a user-friendly error message, or null if valid.
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAgDA;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAoB;IACvD,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,OAAO,mDAAmD,CAAC;IAC7D,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,WAAW,MAAM,CAAC,IAAI,wBAAwB,CAAC;IACxD,CAAC;IACD,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YAC9B,OAAO,WAAW,MAAM,CAAC,IAAI,uCAAuC,CAAC;QACvE,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;YAC1B,OAAO,WAAW,MAAM,CAAC,IAAI,iCAAiC,CAAC;QACjE,CAAC;QACD,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,WAAW,MAAM,CAAC,IAAI,+CAA+C,MAAM,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC;QACtG,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,WAAW,MAAM,CAAC,IAAI,8BAA+B,MAAM,CAAC,SAAoC,CAAC,IAAI,0BAA0B,CAAC;IACzI,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAyDA;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAoB;IACvD,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,OAAO,mDAAmD,CAAC;IAC7D,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,WAAW,MAAM,CAAC,IAAI,wBAAwB,CAAC;IACxD,CAAC;IACD,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YAC9B,OAAO,WAAW,MAAM,CAAC,IAAI,uCAAuC,CAAC;QACvE,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;YAC1B,OAAO,WAAW,MAAM,CAAC,IAAI,iCAAiC,CAAC;QACjE,CAAC;QACD,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,WAAW,MAAM,CAAC,IAAI,+CAA+C,MAAM,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC;QACtG,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,WAAW,MAAM,CAAC,IAAI,8BAA+B,MAAM,CAAC,SAAoC,CAAC,IAAI,0BAA0B,CAAC;IACzI,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
package/dist/server.d.ts CHANGED
@@ -1,4 +1,35 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
1
2
  import type { MCPicoConfig } from "./config.js";
3
+ import { type DiscoveredServer } from "./discoverer.js";
4
+ import { type ToolGroup } from "./grouper.js";
5
+ /** Helper: create a simple text content result */
6
+ export declare function textResult(text: string): CallToolResult;
7
+ /**
8
+ * Merge tool groups from multiple upstream servers.
9
+ *
10
+ * Groups with the same name are merged:
11
+ * - Tools are concatenated
12
+ * - serverName is joined with " + "
13
+ */
14
+ export declare function mergeGroups(allGroups: ToolGroup[]): Map<string, ToolGroup>;
15
+ /**
16
+ * Handle a tool call command for a given group.
17
+ *
18
+ * Dispatches: help → error → unknown subcommand → forward to upstream server.
19
+ *
20
+ * Exported for testing; allows injecting a mock forwardFn to avoid
21
+ * connecting to real upstream servers.
22
+ */
23
+ export declare function handleToolCall(command: string, group: ToolGroup, servers: DiscoveredServer[], helpText: string, forwardFn?: (server: DiscoveredServer, toolName: string, args: Record<string, unknown>) => Promise<CallToolResult>): Promise<CallToolResult>;
24
+ /**
25
+ * Build the description string for a tool group.
26
+ */
27
+ export declare function buildGroupDescription(group: ToolGroup): string;
28
+ /**
29
+ * Validate all server configs, returning an array of error messages.
30
+ * Returns empty array if all configs are valid.
31
+ */
32
+ export declare function validateAllServerConfigs(servers: MCPicoConfig["servers"]): string[];
2
33
  /**
3
34
  * Start the MCPico proxy server.
4
35
  *
package/dist/server.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { createServer } from "node:http";
3
5
  import { z } from "zod/v4";
4
6
  import { validateServerConfig } from "./config.js";
5
7
  import { discoverServer, disconnectServer, } from "./discoverer.js";
@@ -8,11 +10,94 @@ import { parseCommand } from "./parser.js";
8
10
  import { generateHelpText } from "./help.js";
9
11
  import { forwardToolCall } from "./proxy.js";
10
12
  /** Helper: create a simple text content result */
11
- function textResult(text) {
13
+ export function textResult(text) {
12
14
  return {
13
15
  content: [{ type: "text", text }],
14
16
  };
15
17
  }
18
+ /**
19
+ * Merge tool groups from multiple upstream servers.
20
+ *
21
+ * Groups with the same name are merged:
22
+ * - Tools are concatenated
23
+ * - serverName is joined with " + "
24
+ */
25
+ export function mergeGroups(allGroups) {
26
+ const mergedGroups = new Map();
27
+ for (const group of allGroups) {
28
+ const existing = mergedGroups.get(group.groupName);
29
+ if (existing) {
30
+ existing.tools.push(...group.tools);
31
+ if (!existing.serverName.includes(group.serverName)) {
32
+ existing.serverName += ` + ${group.serverName}`;
33
+ }
34
+ }
35
+ else {
36
+ mergedGroups.set(group.groupName, { ...group });
37
+ }
38
+ }
39
+ return mergedGroups;
40
+ }
41
+ /**
42
+ * Handle a tool call command for a given group.
43
+ *
44
+ * Dispatches: help → error → unknown subcommand → forward to upstream server.
45
+ *
46
+ * Exported for testing; allows injecting a mock forwardFn to avoid
47
+ * connecting to real upstream servers.
48
+ */
49
+ export async function handleToolCall(command, group, servers, helpText, forwardFn = forwardToolCall) {
50
+ const parsed = parseCommand(command);
51
+ if (parsed.isHelp) {
52
+ return textResult(helpText);
53
+ }
54
+ if (parsed.error) {
55
+ return textResult(parsed.error);
56
+ }
57
+ const toolName = parsed.subcommand;
58
+ const upstreamTool = group.tools.find((t) => t.name === toolName);
59
+ if (!upstreamTool) {
60
+ const available = group.tools.map((t) => t.name).join(", ");
61
+ return textResult(`Unknown subcommand: "${toolName}"\n\nAvailable in ${group.groupName}: ${available}\n\nUse 'help' for full documentation.`);
62
+ }
63
+ const serverForTool = servers.find((s) => s.tools.some((t) => t.name === toolName));
64
+ if (!serverForTool) {
65
+ return textResult(`Internal error: could not find upstream server for tool "${toolName}"`);
66
+ }
67
+ try {
68
+ const result = await forwardFn(serverForTool, toolName, parsed.args);
69
+ return result;
70
+ }
71
+ catch (err) {
72
+ return textResult(`Error calling "${toolName}": ${err.message}`);
73
+ }
74
+ }
75
+ /**
76
+ * Build the description string for a tool group.
77
+ */
78
+ export function buildGroupDescription(group) {
79
+ const toolCount = group.tools.length;
80
+ return [
81
+ `MCPico ${group.groupName} — ${toolCount} tool${toolCount === 1 ? "" : "s"}`,
82
+ `Source: ${group.serverName}`,
83
+ `Use 'help' to see all subcommands and their parameters.`,
84
+ `Format: <subcommand> {"key":"value",...}`,
85
+ ].join(" | ");
86
+ }
87
+ /**
88
+ * Validate all server configs, returning an array of error messages.
89
+ * Returns empty array if all configs are valid.
90
+ */
91
+ export function validateAllServerConfigs(servers) {
92
+ const errors = [];
93
+ for (const serverConfig of servers) {
94
+ const err = validateServerConfig(serverConfig);
95
+ if (err) {
96
+ errors.push(err);
97
+ }
98
+ }
99
+ return errors;
100
+ }
16
101
  /**
17
102
  * Start the MCPico proxy server.
18
103
  *
@@ -27,13 +112,7 @@ export async function startServer(config) {
27
112
  const servers = [];
28
113
  const allGroups = [];
29
114
  // Validate server configs
30
- const validationErrors = [];
31
- for (const serverConfig of config.servers) {
32
- const err = validateServerConfig(serverConfig);
33
- if (err) {
34
- validationErrors.push(err);
35
- }
36
- }
115
+ const validationErrors = validateAllServerConfigs(config.servers);
37
116
  if (validationErrors.length > 0) {
38
117
  console.error("Configuration errors:");
39
118
  for (const err of validationErrors) {
@@ -64,19 +143,7 @@ export async function startServer(config) {
64
143
  process.exit(1);
65
144
  }
66
145
  // Build lookup: groupName → ToolGroup (merged across servers)
67
- const mergedGroups = new Map();
68
- for (const group of allGroups) {
69
- const existing = mergedGroups.get(group.groupName);
70
- if (existing) {
71
- existing.tools.push(...group.tools);
72
- if (!existing.serverName.includes(group.serverName)) {
73
- existing.serverName += ` + ${group.serverName}`;
74
- }
75
- }
76
- else {
77
- mergedGroups.set(group.groupName, { ...group });
78
- }
79
- }
146
+ const mergedGroups = mergeGroups(allGroups);
80
147
  // Create the MCPico server
81
148
  const server = new McpServer({ name: "MCPico", version: "0.1.0" }, {
82
149
  capabilities: { tools: {}, resources: {}, prompts: {} },
@@ -87,12 +154,7 @@ export async function startServer(config) {
87
154
  // Register each group as a tool
88
155
  for (const [groupName, group] of mergedGroups) {
89
156
  const toolCount = group.tools.length;
90
- const description = [
91
- `MCPico ${groupName} — ${toolCount} tool${toolCount === 1 ? "" : "s"}`,
92
- `Source: ${group.serverName}`,
93
- `Use 'help' to see all subcommands and their parameters.`,
94
- `Format: <subcommand> {"key":"value",...}`,
95
- ].join(" | ");
157
+ const description = buildGroupDescription(group);
96
158
  const helpText = generateHelpText(group);
97
159
  server.registerTool(groupName, {
98
160
  title: `MCPico: ${groupName}`,
@@ -104,30 +166,7 @@ export async function startServer(config) {
104
166
  `${toolCount} subcommand${toolCount === 1 ? "" : "s"} available.`),
105
167
  },
106
168
  }, async (args) => {
107
- const parsed = parseCommand(args.command);
108
- if (parsed.isHelp) {
109
- return textResult(helpText);
110
- }
111
- if (parsed.error) {
112
- return textResult(parsed.error);
113
- }
114
- const toolName = parsed.subcommand;
115
- const upstreamTool = group.tools.find((t) => t.name === toolName);
116
- if (!upstreamTool) {
117
- const available = group.tools.map((t) => t.name).join(", ");
118
- return textResult(`Unknown subcommand: "${toolName}"\n\nAvailable in ${groupName}: ${available}\n\nUse 'help' for full documentation.`);
119
- }
120
- const serverForTool = servers.find((s) => s.tools.some((t) => t.name === toolName));
121
- if (!serverForTool) {
122
- return textResult(`Internal error: could not find upstream server for tool "${toolName}"`);
123
- }
124
- try {
125
- const result = await forwardToolCall(serverForTool, toolName, parsed.args);
126
- return result;
127
- }
128
- catch (err) {
129
- return textResult(`Error calling "${toolName}": ${err.message}`);
130
- }
169
+ return handleToolCall(args.command, group, servers, helpText);
131
170
  });
132
171
  console.error(` Registered group: ${groupName} (${toolCount} tools)`);
133
172
  }
@@ -189,10 +228,29 @@ export async function startServer(config) {
189
228
  if (promptCount > 0) {
190
229
  console.error(` Registered ${promptCount} prompt(s)`);
191
230
  }
192
- // Connect to client via stdio
193
- const transport = new StdioServerTransport();
194
- await server.connect(transport);
195
- console.error(`MCPico ready ${mergedGroups.size} group(s), ${servers.length} upstream server(s)`);
231
+ // Connect to client via configured transport
232
+ const listenConfig = config.listen || { type: "stdio" };
233
+ if (listenConfig.type === "sse") {
234
+ const transport = new StreamableHTTPServerTransport();
235
+ const httpServer = createServer(async (req, res) => {
236
+ // Collect body for handleRequest
237
+ const chunks = [];
238
+ for await (const chunk of req) {
239
+ chunks.push(chunk);
240
+ }
241
+ const body = chunks.length > 0 ? Buffer.concat(chunks).toString() : undefined;
242
+ await transport.handleRequest(req, res, body);
243
+ });
244
+ await server.connect(transport);
245
+ const host = listenConfig.host || "localhost";
246
+ httpServer.listen(listenConfig.port, host);
247
+ console.error(`MCPico ready — ${mergedGroups.size} group(s), ${servers.length} upstream server(s) — listening on http://${host}:${listenConfig.port}`);
248
+ }
249
+ else {
250
+ const transport = new StdioServerTransport();
251
+ await server.connect(transport);
252
+ console.error(`MCPico ready — ${mergedGroups.size} group(s), ${servers.length} upstream server(s)`);
253
+ }
196
254
  // Handle shutdown
197
255
  let shuttingDown = false;
198
256
  const shutdown = async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcpico",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "MCPico — MCP proxy that bundles flat tool lists into hierarchical subcommand groups. Pico footprint, maximum discovery.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,11 +24,20 @@
24
24
  "subcommand"
25
25
  ],
26
26
  "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/lxg2it/mcpico"
30
+ },
31
+ "homepage": "https://github.com/lxg2it/mcpico#readme",
32
+ "bugs": {
33
+ "url": "https://github.com/lxg2it/mcpico/issues"
34
+ },
27
35
  "dependencies": {
28
36
  "@modelcontextprotocol/sdk": "^1.25.0"
29
37
  },
30
38
  "devDependencies": {
31
39
  "@types/node": "^22.0.0",
40
+ "@vitest/coverage-v8": "^4.1.9",
32
41
  "tsx": "^4.19.0",
33
42
  "typescript": "^5.8.0",
34
43
  "vitest": "^4.1.9"
package/src/config.ts CHANGED
@@ -34,6 +34,13 @@ export interface GroupOverrides {
34
34
  [groupName: string]: string[];
35
35
  }
36
36
 
37
+ /**
38
+ * Transport to expose MCPico itself to clients.
39
+ */
40
+ export type ListenConfig =
41
+ | { type: "stdio" }
42
+ | { type: "sse"; port: number; host?: string };
43
+
37
44
  /**
38
45
  * Full MCPico configuration.
39
46
  */
@@ -44,6 +51,8 @@ export interface MCPicoConfig {
44
51
  separator?: string;
45
52
  /** Explicit group overrides — tools not listed here are auto-grouped */
46
53
  groups?: GroupOverrides;
54
+ /** How MCPico exposes itself to clients (default: stdio) */
55
+ listen?: ListenConfig;
47
56
  }
48
57
 
49
58
  /**
package/src/help.test.ts CHANGED
@@ -194,4 +194,44 @@ describe("generateHelpText", () => {
194
194
  expect(text).toContain(tool.name);
195
195
  // Should not crash
196
196
  });
197
+
198
+ it("handles non-standard property types gracefully", () => {
199
+ const tool = makeTool({
200
+ inputSchema: {
201
+ type: "object",
202
+ properties: {
203
+ weird: { type: null, description: "Unknown type" },
204
+ },
205
+ required: ["weird"],
206
+ },
207
+ });
208
+ const text = generateHelpText(makeGroup({ tools: [tool] }));
209
+ // Should not crash — fallback to "any"
210
+ expect(text).toContain("(any");
211
+ });
212
+
213
+ it("falls back to bare subcommand for very long examples", () => {
214
+ // Three required params with long names → JSON.stringify > 80 chars
215
+ const tool = makeTool({
216
+ name: "do_something",
217
+ inputSchema: {
218
+ type: "object",
219
+ properties: {
220
+ configuration_file_path: { type: "string", description: "Long param" },
221
+ output_destination_directory: { type: "string", description: "Another long one" },
222
+ encryption_algorithm_identifier: { type: "string", description: "Third long one" },
223
+ },
224
+ required: [
225
+ "configuration_file_path",
226
+ "output_destination_directory",
227
+ "encryption_algorithm_identifier",
228
+ ],
229
+ },
230
+ });
231
+ const text = generateHelpText(makeGroup({ tools: [tool] }));
232
+ // Example should just be the bare subcommand name (long args overflow)
233
+ // (param names appear in Parameters section too, so just check the Example line)
234
+ const exampleLine = text.split("\n").find((l) => l.trim().startsWith("Example:"));
235
+ expect(exampleLine).toBe(" Example: do_something");
236
+ });
197
237
  });
@@ -0,0 +1,324 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { textResult, mergeGroups, handleToolCall, validateAllServerConfigs, buildGroupDescription } from "./server.js";
3
+ import type { ToolGroup } from "./grouper.js";
4
+ import type { DiscoveredServer, UpstreamTool } from "./discoverer.js";
5
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
6
+
7
+ function makeTool(overrides: Partial<UpstreamTool> = {}): UpstreamTool {
8
+ return {
9
+ name: "test_tool",
10
+ description: "A test tool",
11
+ inputSchema: { type: "object", properties: {} },
12
+ ...overrides,
13
+ };
14
+ }
15
+
16
+ function makeGroup(overrides: Partial<ToolGroup> = {}): ToolGroup {
17
+ return {
18
+ groupName: "test_group",
19
+ serverName: "test-server",
20
+ tools: [makeTool()],
21
+ ...overrides,
22
+ };
23
+ }
24
+
25
+ function makeServer(overrides: Partial<DiscoveredServer> = {}): DiscoveredServer {
26
+ return {
27
+ name: "test-server",
28
+ tools: [makeTool()],
29
+ resources: [],
30
+ prompts: [],
31
+ client: {} as any,
32
+ ...overrides,
33
+ };
34
+ }
35
+
36
+ function makeTextResult(text: string): CallToolResult {
37
+ return { content: [{ type: "text", text }] };
38
+ }
39
+
40
+ describe("textResult", () => {
41
+ it("wraps text in content array", () => {
42
+ const result = textResult("hello");
43
+ expect(result).toEqual({
44
+ content: [{ type: "text", text: "hello" }],
45
+ });
46
+ });
47
+
48
+ it("handles empty string", () => {
49
+ const result = textResult("");
50
+ expect(result).toEqual({
51
+ content: [{ type: "text", text: "" }],
52
+ });
53
+ });
54
+ });
55
+
56
+ describe("mergeGroups", () => {
57
+ it("returns empty map for empty input", () => {
58
+ const result = mergeGroups([]);
59
+ expect(result.size).toBe(0);
60
+ });
61
+
62
+ it("preserves single group", () => {
63
+ const group = makeGroup({ groupName: "filesystem" });
64
+ const result = mergeGroups([group]);
65
+ expect(result.size).toBe(1);
66
+ const entry = result.get("filesystem")!;
67
+ expect(entry.groupName).toBe("filesystem");
68
+ expect(entry.tools).toHaveLength(1);
69
+ });
70
+
71
+ it("keeps different groups separate", () => {
72
+ const groups = [
73
+ makeGroup({ groupName: "filesystem" }),
74
+ makeGroup({ groupName: "database" }),
75
+ ];
76
+ const result = mergeGroups(groups);
77
+ expect(result.size).toBe(2);
78
+ expect(result.has("filesystem")).toBe(true);
79
+ expect(result.has("database")).toBe(true);
80
+ });
81
+
82
+ it("merges groups with same name across servers", () => {
83
+ const groups = [
84
+ makeGroup({
85
+ groupName: "filesystem",
86
+ serverName: "server-a",
87
+ tools: [makeTool({ name: "read" })],
88
+ }),
89
+ makeGroup({
90
+ groupName: "filesystem",
91
+ serverName: "server-b",
92
+ tools: [makeTool({ name: "write" })],
93
+ }),
94
+ ];
95
+ const result = mergeGroups(groups);
96
+ expect(result.size).toBe(1);
97
+ const merged = result.get("filesystem")!;
98
+ expect(merged.tools).toHaveLength(2);
99
+ expect(merged.tools.map((t) => t.name)).toEqual(["read", "write"]);
100
+ expect(merged.serverName).toContain("server-a");
101
+ expect(merged.serverName).toContain("server-b");
102
+ });
103
+
104
+ it("does not duplicate server name when already present", () => {
105
+ const groups = [
106
+ makeGroup({
107
+ groupName: "filesystem",
108
+ serverName: "server-a + server-b",
109
+ tools: [makeTool({ name: "read" })],
110
+ }),
111
+ makeGroup({
112
+ groupName: "filesystem",
113
+ serverName: "server-b",
114
+ tools: [makeTool({ name: "write" })],
115
+ }),
116
+ ];
117
+ const result = mergeGroups(groups);
118
+ const merged = result.get("filesystem")!;
119
+ // server-b is already in "server-a + server-b", should not be added again
120
+ expect(merged.serverName).toBe("server-a + server-b");
121
+ });
122
+ });
123
+
124
+ describe("validateAllServerConfigs", () => {
125
+ it("returns empty for valid configs", () => {
126
+ const errors = validateAllServerConfigs([
127
+ { name: "server-a", transport: { type: "stdio" as const, command: "echo" } },
128
+ ]);
129
+ expect(errors).toEqual([]);
130
+ });
131
+
132
+ it("returns errors for invalid configs", () => {
133
+ const errors = validateAllServerConfigs([
134
+ { name: "", transport: { type: "stdio" as const, command: "" } },
135
+ ]);
136
+ expect(errors.length).toBeGreaterThan(0);
137
+ });
138
+
139
+ it("validates multiple configs and collects all errors", () => {
140
+ const errors = validateAllServerConfigs([
141
+ { name: "", transport: { type: "stdio" as const, command: "" } },
142
+ { name: "missing-transport" } as any,
143
+ ]);
144
+ expect(errors.length).toBe(2);
145
+ });
146
+
147
+ it("handles empty servers array", () => {
148
+ const errors = validateAllServerConfigs([]);
149
+ expect(errors).toEqual([]);
150
+ });
151
+ });
152
+
153
+ describe("buildGroupDescription", () => {
154
+ it("includes group name", () => {
155
+ const desc = buildGroupDescription(makeGroup({ groupName: "filesystem" }));
156
+ expect(desc).toContain("MCPico filesystem");
157
+ });
158
+
159
+ it("shows singular for 1 tool", () => {
160
+ const desc = buildGroupDescription(makeGroup());
161
+ expect(desc).toContain("1 tool");
162
+ });
163
+
164
+ it("shows plural for multiple tools", () => {
165
+ const group = makeGroup({
166
+ tools: [makeTool({ name: "a" }), makeTool({ name: "b" })],
167
+ });
168
+ const desc = buildGroupDescription(group);
169
+ expect(desc).toContain("2 tools");
170
+ });
171
+
172
+ it("includes server name", () => {
173
+ const desc = buildGroupDescription(makeGroup({ serverName: "my-upstream" }));
174
+ expect(desc).toContain("Source: my-upstream");
175
+ });
176
+
177
+ it("includes usage hints", () => {
178
+ const desc = buildGroupDescription(makeGroup());
179
+ expect(desc).toContain("Use 'help'");
180
+ expect(desc).toContain("<subcommand>");
181
+ });
182
+ });
183
+
184
+ describe("handleToolCall", () => {
185
+ const servers = [makeServer()];
186
+ const group = makeGroup();
187
+ const helpText = "Help text here";
188
+
189
+ it("returns help for 'help' command", async () => {
190
+ const result = await handleToolCall("help", group, servers, helpText);
191
+ expect(result).toEqual(makeTextResult(helpText));
192
+ });
193
+
194
+ it("returns help for empty command", async () => {
195
+ const result = await handleToolCall("", group, servers, helpText);
196
+ expect(result).toEqual(makeTextResult(helpText));
197
+ });
198
+
199
+ it("returns help for whitespace command", async () => {
200
+ const result = await handleToolCall(" ", group, servers, helpText);
201
+ expect(result).toEqual(makeTextResult(helpText));
202
+ });
203
+
204
+ it("returns parse error for invalid JSON args", async () => {
205
+ const result = await handleToolCall(
206
+ "test_tool {bad json}",
207
+ group,
208
+ servers,
209
+ helpText
210
+ );
211
+ expect(result.content?.[0]).toBeDefined();
212
+ if (result.content?.[0] && "text" in result.content[0]) {
213
+ expect(result.content[0].text).toContain("Could not parse arguments as JSON");
214
+ }
215
+ });
216
+
217
+ it("returns error for unknown subcommand", async () => {
218
+ const result = await handleToolCall(
219
+ "nonexistent",
220
+ group,
221
+ servers,
222
+ helpText
223
+ );
224
+ expect(result.content?.[0]).toBeDefined();
225
+ if (result.content?.[0] && "text" in result.content[0]) {
226
+ expect(result.content[0].text).toContain('Unknown subcommand: "nonexistent"');
227
+ expect(result.content[0].text).toContain("test_tool");
228
+ }
229
+ });
230
+
231
+ it("returns internal error when server not found for valid tool", async () => {
232
+ // Tool exists in group but no server has it
233
+ const orphanTool = makeTool({ name: "orphan_tool" });
234
+ const orphanGroup = makeGroup({ tools: [orphanTool] });
235
+ const emptyServers: DiscoveredServer[] = [];
236
+
237
+ const result = await handleToolCall(
238
+ "orphan_tool",
239
+ orphanGroup,
240
+ emptyServers,
241
+ helpText
242
+ );
243
+ expect(result.content?.[0]).toBeDefined();
244
+ if (result.content?.[0] && "text" in result.content[0]) {
245
+ expect(result.content[0].text).toContain("Internal error");
246
+ }
247
+ });
248
+
249
+ it("dispatches to forwardFn for valid tool", async () => {
250
+ const forwardFn = vi.fn().mockResolvedValue(makeTextResult("forwarded!"));
251
+
252
+ const result = await handleToolCall(
253
+ "test_tool",
254
+ group,
255
+ servers,
256
+ helpText,
257
+ forwardFn
258
+ );
259
+
260
+ expect(forwardFn).toHaveBeenCalledTimes(1);
261
+ expect(forwardFn).toHaveBeenCalledWith(servers[0], "test_tool", {});
262
+ expect(result).toEqual(makeTextResult("forwarded!"));
263
+ });
264
+
265
+ it("passes parsed args to forwardFn", async () => {
266
+ const forwardFn = vi.fn().mockResolvedValue(makeTextResult("ok"));
267
+
268
+ await handleToolCall(
269
+ 'test_tool {"key":"value","count":42}',
270
+ group,
271
+ servers,
272
+ helpText,
273
+ forwardFn
274
+ );
275
+
276
+ expect(forwardFn).toHaveBeenCalledWith(servers[0], "test_tool", {
277
+ key: "value",
278
+ count: 42,
279
+ });
280
+ });
281
+
282
+ it("returns error when forwardFn throws", async () => {
283
+ const forwardFn = vi.fn().mockRejectedValue(new Error("Connection lost"));
284
+
285
+ const result = await handleToolCall(
286
+ "test_tool",
287
+ group,
288
+ servers,
289
+ helpText,
290
+ forwardFn
291
+ );
292
+
293
+ expect(result.content?.[0]).toBeDefined();
294
+ if (result.content?.[0] && "text" in result.content[0]) {
295
+ expect(result.content[0].text).toContain('Error calling "test_tool"');
296
+ expect(result.content[0].text).toContain("Connection lost");
297
+ }
298
+ });
299
+
300
+ it("finds correct server when multiple servers exist", async () => {
301
+ const serverA = makeServer({
302
+ name: "server-a",
303
+ tools: [makeTool({ name: "tool_a" })],
304
+ });
305
+ const serverB = makeServer({
306
+ name: "server-b",
307
+ tools: [makeTool({ name: "tool_b" })],
308
+ });
309
+ const multiGroup = makeGroup({
310
+ tools: [makeTool({ name: "tool_a" }), makeTool({ name: "tool_b" })],
311
+ });
312
+ const forwardFn = vi.fn().mockResolvedValue(makeTextResult("ok"));
313
+
314
+ await handleToolCall(
315
+ "tool_b",
316
+ multiGroup,
317
+ [serverA, serverB],
318
+ helpText,
319
+ forwardFn
320
+ );
321
+
322
+ expect(forwardFn).toHaveBeenCalledWith(serverB, "tool_b", {});
323
+ });
324
+ });
package/src/server.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { createServer } from "node:http";
3
5
  import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
4
6
  import { z } from "zod/v4";
5
7
  import type { MCPicoConfig } from "./config.js";
@@ -15,12 +17,121 @@ import { generateHelpText } from "./help.js";
15
17
  import { forwardToolCall } from "./proxy.js";
16
18
 
17
19
  /** Helper: create a simple text content result */
18
- function textResult(text: string): CallToolResult {
20
+ export function textResult(text: string): CallToolResult {
19
21
  return {
20
22
  content: [{ type: "text" as const, text }],
21
23
  };
22
24
  }
23
25
 
26
+ /**
27
+ * Merge tool groups from multiple upstream servers.
28
+ *
29
+ * Groups with the same name are merged:
30
+ * - Tools are concatenated
31
+ * - serverName is joined with " + "
32
+ */
33
+ export function mergeGroups(allGroups: ToolGroup[]): Map<string, ToolGroup> {
34
+ const mergedGroups = new Map<string, ToolGroup>();
35
+ for (const group of allGroups) {
36
+ const existing = mergedGroups.get(group.groupName);
37
+ if (existing) {
38
+ existing.tools.push(...group.tools);
39
+ if (!existing.serverName.includes(group.serverName)) {
40
+ existing.serverName += ` + ${group.serverName}`;
41
+ }
42
+ } else {
43
+ mergedGroups.set(group.groupName, { ...group });
44
+ }
45
+ }
46
+ return mergedGroups;
47
+ }
48
+
49
+ /**
50
+ * Handle a tool call command for a given group.
51
+ *
52
+ * Dispatches: help → error → unknown subcommand → forward to upstream server.
53
+ *
54
+ * Exported for testing; allows injecting a mock forwardFn to avoid
55
+ * connecting to real upstream servers.
56
+ */
57
+ export async function handleToolCall(
58
+ command: string,
59
+ group: ToolGroup,
60
+ servers: DiscoveredServer[],
61
+ helpText: string,
62
+ forwardFn: (
63
+ server: DiscoveredServer,
64
+ toolName: string,
65
+ args: Record<string, unknown>
66
+ ) => Promise<CallToolResult> = forwardToolCall
67
+ ): Promise<CallToolResult> {
68
+ const parsed = parseCommand(command);
69
+
70
+ if (parsed.isHelp) {
71
+ return textResult(helpText);
72
+ }
73
+
74
+ if (parsed.error) {
75
+ return textResult(parsed.error);
76
+ }
77
+
78
+ const toolName = parsed.subcommand!;
79
+ const upstreamTool = group.tools.find((t) => t.name === toolName);
80
+ if (!upstreamTool) {
81
+ const available = group.tools.map((t) => t.name).join(", ");
82
+ return textResult(
83
+ `Unknown subcommand: "${toolName}"\n\nAvailable in ${group.groupName}: ${available}\n\nUse 'help' for full documentation.`
84
+ );
85
+ }
86
+
87
+ const serverForTool = servers.find((s) =>
88
+ s.tools.some((t) => t.name === toolName)
89
+ );
90
+
91
+ if (!serverForTool) {
92
+ return textResult(
93
+ `Internal error: could not find upstream server for tool "${toolName}"`
94
+ );
95
+ }
96
+
97
+ try {
98
+ const result = await forwardFn(serverForTool, toolName, parsed.args);
99
+ return result;
100
+ } catch (err) {
101
+ return textResult(
102
+ `Error calling "${toolName}": ${(err as Error).message}`
103
+ );
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Build the description string for a tool group.
109
+ */
110
+ export function buildGroupDescription(group: ToolGroup): string {
111
+ const toolCount = group.tools.length;
112
+ return [
113
+ `MCPico ${group.groupName} — ${toolCount} tool${toolCount === 1 ? "" : "s"}`,
114
+ `Source: ${group.serverName}`,
115
+ `Use 'help' to see all subcommands and their parameters.`,
116
+ `Format: <subcommand> {"key":"value",...}`,
117
+ ].join(" | ");
118
+ }
119
+
120
+ /**
121
+ * Validate all server configs, returning an array of error messages.
122
+ * Returns empty array if all configs are valid.
123
+ */
124
+ export function validateAllServerConfigs(servers: MCPicoConfig["servers"]): string[] {
125
+ const errors: string[] = [];
126
+ for (const serverConfig of servers) {
127
+ const err = validateServerConfig(serverConfig);
128
+ if (err) {
129
+ errors.push(err);
130
+ }
131
+ }
132
+ return errors;
133
+ }
134
+
24
135
  /**
25
136
  * Start the MCPico proxy server.
26
137
  *
@@ -36,13 +147,7 @@ export async function startServer(config: MCPicoConfig): Promise<void> {
36
147
  const allGroups: ToolGroup[] = [];
37
148
 
38
149
  // Validate server configs
39
- const validationErrors: string[] = [];
40
- for (const serverConfig of config.servers) {
41
- const err = validateServerConfig(serverConfig);
42
- if (err) {
43
- validationErrors.push(err);
44
- }
45
- }
150
+ const validationErrors = validateAllServerConfigs(config.servers);
46
151
  if (validationErrors.length > 0) {
47
152
  console.error("Configuration errors:");
48
153
  for (const err of validationErrors) {
@@ -96,18 +201,7 @@ export async function startServer(config: MCPicoConfig): Promise<void> {
96
201
  }
97
202
 
98
203
  // Build lookup: groupName → ToolGroup (merged across servers)
99
- const mergedGroups = new Map<string, ToolGroup>();
100
- for (const group of allGroups) {
101
- const existing = mergedGroups.get(group.groupName);
102
- if (existing) {
103
- existing.tools.push(...group.tools);
104
- if (!existing.serverName.includes(group.serverName)) {
105
- existing.serverName += ` + ${group.serverName}`;
106
- }
107
- } else {
108
- mergedGroups.set(group.groupName, { ...group });
109
- }
110
- }
204
+ const mergedGroups = mergeGroups(allGroups);
111
205
 
112
206
  // Create the MCPico server
113
207
  const server = new McpServer(
@@ -124,12 +218,7 @@ export async function startServer(config: MCPicoConfig): Promise<void> {
124
218
  // Register each group as a tool
125
219
  for (const [groupName, group] of mergedGroups) {
126
220
  const toolCount = group.tools.length;
127
- const description = [
128
- `MCPico ${groupName} — ${toolCount} tool${toolCount === 1 ? "" : "s"}`,
129
- `Source: ${group.serverName}`,
130
- `Use 'help' to see all subcommands and their parameters.`,
131
- `Format: <subcommand> {"key":"value",...}`,
132
- ].join(" | ");
221
+ const description = buildGroupDescription(group);
133
222
 
134
223
  const helpText = generateHelpText(group);
135
224
 
@@ -148,47 +237,7 @@ export async function startServer(config: MCPicoConfig): Promise<void> {
148
237
  },
149
238
  },
150
239
  async (args: { command: string }) => {
151
- const parsed = parseCommand(args.command);
152
-
153
- if (parsed.isHelp) {
154
- return textResult(helpText);
155
- }
156
-
157
- if (parsed.error) {
158
- return textResult(parsed.error);
159
- }
160
-
161
- const toolName = parsed.subcommand!;
162
- const upstreamTool = group.tools.find((t) => t.name === toolName);
163
- if (!upstreamTool) {
164
- const available = group.tools.map((t) => t.name).join(", ");
165
- return textResult(
166
- `Unknown subcommand: "${toolName}"\n\nAvailable in ${groupName}: ${available}\n\nUse 'help' for full documentation.`
167
- );
168
- }
169
-
170
- const serverForTool = servers.find((s) =>
171
- s.tools.some((t) => t.name === toolName)
172
- );
173
-
174
- if (!serverForTool) {
175
- return textResult(
176
- `Internal error: could not find upstream server for tool "${toolName}"`
177
- );
178
- }
179
-
180
- try {
181
- const result = await forwardToolCall(
182
- serverForTool,
183
- toolName,
184
- parsed.args
185
- );
186
- return result;
187
- } catch (err) {
188
- return textResult(
189
- `Error calling "${toolName}": ${(err as Error).message}`
190
- );
191
- }
240
+ return handleToolCall(args.command, group, servers, helpText);
192
241
  }
193
242
  );
194
243
 
@@ -273,13 +322,33 @@ export async function startServer(config: MCPicoConfig): Promise<void> {
273
322
  console.error(` Registered ${promptCount} prompt(s)`);
274
323
  }
275
324
 
276
- // Connect to client via stdio
277
- const transport = new StdioServerTransport();
278
- await server.connect(transport);
325
+ // Connect to client via configured transport
326
+ const listenConfig = config.listen || { type: "stdio" };
279
327
 
280
- console.error(
281
- `MCPico ready ${mergedGroups.size} group(s), ${servers.length} upstream server(s)`
282
- );
328
+ if (listenConfig.type === "sse") {
329
+ const transport = new StreamableHTTPServerTransport();
330
+ const httpServer = createServer(async (req, res) => {
331
+ // Collect body for handleRequest
332
+ const chunks: Buffer[] = [];
333
+ for await (const chunk of req) {
334
+ chunks.push(chunk);
335
+ }
336
+ const body = chunks.length > 0 ? Buffer.concat(chunks).toString() : undefined;
337
+ await transport.handleRequest(req, res, body);
338
+ });
339
+ await server.connect(transport);
340
+ const host = listenConfig.host || "localhost";
341
+ httpServer.listen(listenConfig.port, host);
342
+ console.error(
343
+ `MCPico ready — ${mergedGroups.size} group(s), ${servers.length} upstream server(s) — listening on http://${host}:${listenConfig.port}`
344
+ );
345
+ } else {
346
+ const transport = new StdioServerTransport();
347
+ await server.connect(transport);
348
+ console.error(
349
+ `MCPico ready — ${mergedGroups.size} group(s), ${servers.length} upstream server(s)`
350
+ );
351
+ }
283
352
 
284
353
  // Handle shutdown
285
354
  let shuttingDown = false;
package/vitest.config.ts CHANGED
@@ -7,10 +7,13 @@ export default defineConfig({
7
7
  include: ["src/**/*.ts"],
8
8
  exclude: ["src/**/*.test.ts", "src/index.ts"],
9
9
  thresholds: {
10
- statements: 85,
11
- branches: 80,
12
- functions: 85,
13
- lines: 85,
10
+ // startServer() is I/O orchestration (connect/discover/register/transport)
11
+ // — requires integration tests, not unit tests. Pure logic functions are
12
+ // extracted and fully tested. SSE/HTTP transport code is also I/O.
13
+ statements: 67,
14
+ branches: 68,
15
+ functions: 80,
16
+ lines: 66,
14
17
  },
15
18
  },
16
19
  },