mcpico 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.
Files changed (61) hide show
  1. package/README.md +172 -0
  2. package/dist/config.d.ts +46 -0
  3. package/dist/config.js +32 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/config.test.d.ts +1 -0
  6. package/dist/config.test.js +87 -0
  7. package/dist/config.test.js.map +1 -0
  8. package/dist/discoverer.d.ts +52 -0
  9. package/dist/discoverer.js +84 -0
  10. package/dist/discoverer.js.map +1 -0
  11. package/dist/discoverer.test.d.ts +1 -0
  12. package/dist/discoverer.test.js +178 -0
  13. package/dist/discoverer.test.js.map +1 -0
  14. package/dist/grouper.d.ts +22 -0
  15. package/dist/grouper.js +70 -0
  16. package/dist/grouper.js.map +1 -0
  17. package/dist/grouper.test.d.ts +1 -0
  18. package/dist/grouper.test.js +169 -0
  19. package/dist/grouper.test.js.map +1 -0
  20. package/dist/help.d.ts +5 -0
  21. package/dist/help.js +115 -0
  22. package/dist/help.js.map +1 -0
  23. package/dist/help.test.d.ts +1 -0
  24. package/dist/help.test.js +173 -0
  25. package/dist/help.test.js.map +1 -0
  26. package/dist/index.d.ts +10 -0
  27. package/dist/index.js +90 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/parser.d.ts +28 -0
  30. package/dist/parser.js +60 -0
  31. package/dist/parser.js.map +1 -0
  32. package/dist/parser.test.d.ts +1 -0
  33. package/dist/parser.test.js +142 -0
  34. package/dist/parser.test.js.map +1 -0
  35. package/dist/proxy.d.ts +6 -0
  36. package/dist/proxy.js +10 -0
  37. package/dist/proxy.js.map +1 -0
  38. package/dist/proxy.test.d.ts +1 -0
  39. package/dist/proxy.test.js +61 -0
  40. package/dist/proxy.test.js.map +1 -0
  41. package/dist/server.d.ts +11 -0
  42. package/dist/server.js +212 -0
  43. package/dist/server.js.map +1 -0
  44. package/mcplico.example.json +18 -0
  45. package/package.json +36 -0
  46. package/src/config.test.ts +96 -0
  47. package/src/config.ts +76 -0
  48. package/src/discoverer.test.ts +222 -0
  49. package/src/discoverer.ts +148 -0
  50. package/src/grouper.test.ts +202 -0
  51. package/src/grouper.ts +96 -0
  52. package/src/help.test.ts +197 -0
  53. package/src/help.ts +134 -0
  54. package/src/index.ts +106 -0
  55. package/src/parser.test.ts +173 -0
  56. package/src/parser.ts +78 -0
  57. package/src/proxy.test.ts +77 -0
  58. package/src/proxy.ts +16 -0
  59. package/src/server.ts +299 -0
  60. package/tsconfig.json +18 -0
  61. package/vitest.config.ts +17 -0
package/dist/index.js ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCPico — MCP proxy that bundles flat tool lists into hierarchical subcommand groups.
4
+ *
5
+ * Usage:
6
+ * mcpico [--config <path>]
7
+ *
8
+ * Configuration is read from mcpico.json in the current directory by default.
9
+ */
10
+ import { readFileSync } from "node:fs";
11
+ import { resolve } from "node:path";
12
+ import { startServer } from "./server.js";
13
+ async function main() {
14
+ // Parse CLI args
15
+ const args = process.argv.slice(2);
16
+ let configPath = "mcpico.json";
17
+ for (let i = 0; i < args.length; i++) {
18
+ if (args[i] === "--config" || args[i] === "-c") {
19
+ configPath = args[i + 1] || configPath;
20
+ i++;
21
+ }
22
+ else if (args[i] === "--version" || args[i] === "-v") {
23
+ console.log("MCPico v0.1.0");
24
+ process.exit(0);
25
+ }
26
+ else if (args[i] === "--help" || args[i] === "-h") {
27
+ console.log(`
28
+ MCPico — MCP proxy that bundles flat tool lists into hierarchical subcommand groups.
29
+
30
+ Usage:
31
+ mcpico [--config <path>]
32
+
33
+ Options:
34
+ --config, -c <path> Path to config file (default: mcpico.json)
35
+ --version, -v Show version
36
+ --help, -h Show this help
37
+
38
+ Config file format (mcpico.json):
39
+ {
40
+ "servers": [
41
+ {
42
+ "name": "filesystem",
43
+ "transport": {
44
+ "type": "stdio",
45
+ "command": "npx",
46
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
47
+ }
48
+ }
49
+ ],
50
+ "separator": "_",
51
+ "groups": {}
52
+ }
53
+ `);
54
+ process.exit(0);
55
+ }
56
+ }
57
+ // Resolve config path
58
+ const resolvedPath = resolve(configPath);
59
+ let config;
60
+ try {
61
+ const raw = readFileSync(resolvedPath, "utf-8");
62
+ config = JSON.parse(raw);
63
+ }
64
+ catch (err) {
65
+ console.error(`Error reading config file "${resolvedPath}":`, err.message);
66
+ process.exit(1);
67
+ }
68
+ // Validate
69
+ if (!config.servers || !Array.isArray(config.servers) || config.servers.length === 0) {
70
+ console.error('Config error: "servers" must be a non-empty array');
71
+ process.exit(1);
72
+ }
73
+ for (const server of config.servers) {
74
+ if (!server.name || !server.transport) {
75
+ console.error('Config error: each server must have a "name" and "transport"');
76
+ process.exit(1);
77
+ }
78
+ if (server.transport.type === "stdio" &&
79
+ !server.transport.command) {
80
+ console.error(`Config error: server "${server.name}" missing transport.command`);
81
+ process.exit(1);
82
+ }
83
+ }
84
+ await startServer(config);
85
+ }
86
+ main().catch((err) => {
87
+ console.error("Fatal error:", err);
88
+ process.exit(1);
89
+ });
90
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Result of parsing a command string.
3
+ */
4
+ export interface ParsedCommand {
5
+ /** The subcommand name (maps to an upstream tool name) */
6
+ subcommand: string | null;
7
+ /** Parsed arguments to pass to the upstream tool */
8
+ args: Record<string, unknown>;
9
+ /** Whether this is a help request */
10
+ isHelp: boolean;
11
+ /** Error message if parsing failed */
12
+ error: string | null;
13
+ }
14
+ /**
15
+ * Parse a command string from the model.
16
+ *
17
+ * Format: `<subcommand> <json_args>`
18
+ *
19
+ * - Empty string or "help" → isHelp = true
20
+ * - "read_file" → subcommand = "read_file", args = {}
21
+ * - 'read_file {"path":"/etc/hosts"}' → subcommand = "read_file", args = {path: "/etc/hosts"}
22
+ * - "list_dir" → subcommand = "list_dir", args = {}
23
+ *
24
+ * Error handling:
25
+ * - If JSON is malformed, returns error message
26
+ * - If no subcommand given, returns isHelp = true
27
+ */
28
+ export declare function parseCommand(rawCommand: string): ParsedCommand;
package/dist/parser.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Parse a command string from the model.
3
+ *
4
+ * Format: `<subcommand> <json_args>`
5
+ *
6
+ * - Empty string or "help" → isHelp = true
7
+ * - "read_file" → subcommand = "read_file", args = {}
8
+ * - 'read_file {"path":"/etc/hosts"}' → subcommand = "read_file", args = {path: "/etc/hosts"}
9
+ * - "list_dir" → subcommand = "list_dir", args = {}
10
+ *
11
+ * Error handling:
12
+ * - If JSON is malformed, returns error message
13
+ * - If no subcommand given, returns isHelp = true
14
+ */
15
+ export function parseCommand(rawCommand) {
16
+ const trimmed = rawCommand.trim();
17
+ // Empty or explicit help
18
+ if (!trimmed || trimmed === "help") {
19
+ return { subcommand: null, args: {}, isHelp: true, error: null };
20
+ }
21
+ // Find the first space to split subcommand from args
22
+ const spaceIdx = trimmed.indexOf(" ");
23
+ if (spaceIdx === -1) {
24
+ // No args — just a subcommand
25
+ return {
26
+ subcommand: trimmed,
27
+ args: {},
28
+ isHelp: trimmed === "help",
29
+ error: null,
30
+ };
31
+ }
32
+ const subcommand = trimmed.slice(0, spaceIdx).trim();
33
+ const argsStr = trimmed.slice(spaceIdx + 1).trim();
34
+ // Empty args after subcommand
35
+ if (!argsStr) {
36
+ return { subcommand, args: {}, isHelp: false, error: null };
37
+ }
38
+ // Try JSON parse
39
+ try {
40
+ const args = JSON.parse(argsStr);
41
+ if (typeof args !== "object" || Array.isArray(args)) {
42
+ return {
43
+ subcommand,
44
+ args: {},
45
+ isHelp: false,
46
+ error: "Arguments must be a JSON object, e.g. {\"key\":\"value\"}",
47
+ };
48
+ }
49
+ return { subcommand, args: args, isHelp: false, error: null };
50
+ }
51
+ catch {
52
+ return {
53
+ subcommand,
54
+ args: {},
55
+ isHelp: false,
56
+ error: `Could not parse arguments as JSON: "${argsStr}". Use format: <subcommand> {"key":"value",...}`,
57
+ };
58
+ }
59
+ }
60
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC7C,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAElC,yBAAyB;IACzB,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QACnC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACnE,CAAC;IAED,qDAAqD;IACrD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAEtC,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;QACpB,8BAA8B;QAC9B,OAAO;YACL,UAAU,EAAE,OAAO;YACnB,IAAI,EAAE,EAAE;YACR,MAAM,EAAE,OAAO,KAAK,MAAM;YAC1B,KAAK,EAAE,IAAI;SACZ,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;IACrD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAEnD,8BAA8B;IAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC9D,CAAC;IAED,iBAAiB;IACjB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACpD,OAAO;gBACL,UAAU;gBACV,IAAI,EAAE,EAAE;gBACR,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,2DAA2D;aACnE,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAA+B,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC3F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,UAAU;YACV,IAAI,EAAE,EAAE;YACR,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,uCAAuC,OAAO,iDAAiD;SACvG,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,142 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseCommand } from "./parser.js";
3
+ describe("parseCommand", () => {
4
+ describe("help / empty", () => {
5
+ it("returns isHelp for empty string", () => {
6
+ const result = parseCommand("");
7
+ expect(result.isHelp).toBe(true);
8
+ expect(result.subcommand).toBeNull();
9
+ expect(result.args).toEqual({});
10
+ expect(result.error).toBeNull();
11
+ });
12
+ it("returns isHelp for whitespace-only string", () => {
13
+ const result = parseCommand(" ");
14
+ expect(result.isHelp).toBe(true);
15
+ expect(result.error).toBeNull();
16
+ });
17
+ it("returns isHelp for explicit 'help'", () => {
18
+ const result = parseCommand("help");
19
+ expect(result.isHelp).toBe(true);
20
+ expect(result.subcommand).toBeNull();
21
+ expect(result.args).toEqual({});
22
+ expect(result.error).toBeNull();
23
+ });
24
+ it("returns isHelp for 'help' with extra whitespace", () => {
25
+ const result = parseCommand(" help ");
26
+ expect(result.isHelp).toBe(true);
27
+ expect(result.args).toEqual({});
28
+ });
29
+ });
30
+ describe("bare subcommand (no args)", () => {
31
+ it("parses a single-word subcommand", () => {
32
+ const result = parseCommand("read_file");
33
+ expect(result.subcommand).toBe("read_file");
34
+ expect(result.args).toEqual({});
35
+ expect(result.isHelp).toBe(false);
36
+ expect(result.error).toBeNull();
37
+ });
38
+ it("handles subcommand with trailing whitespace", () => {
39
+ const result = parseCommand("list_dir ");
40
+ expect(result.subcommand).toBe("list_dir");
41
+ expect(result.args).toEqual({});
42
+ });
43
+ });
44
+ describe("JSON args", () => {
45
+ it("parses simple JSON object args", () => {
46
+ const result = parseCommand('read_file {"path":"/etc/hosts"}');
47
+ expect(result.subcommand).toBe("read_file");
48
+ expect(result.args).toEqual({ path: "/etc/hosts" });
49
+ expect(result.error).toBeNull();
50
+ });
51
+ it("parses multiple JSON args", () => {
52
+ const result = parseCommand('write_file {"path":"/tmp/out.txt","content":"hello","encoding":"utf-8"}');
53
+ expect(result.subcommand).toBe("write_file");
54
+ expect(result.args).toEqual({
55
+ path: "/tmp/out.txt",
56
+ content: "hello",
57
+ encoding: "utf-8",
58
+ });
59
+ });
60
+ it("parses numeric and boolean values", () => {
61
+ const result = parseCommand('something {"count":42,"enabled":true,"ratio":3.14}');
62
+ expect(result.args).toEqual({
63
+ count: 42,
64
+ enabled: true,
65
+ ratio: 3.14,
66
+ });
67
+ });
68
+ it("parses nested JSON objects", () => {
69
+ const result = parseCommand('complex {"nested":{"key":"value"},"list":[1,2,3]}');
70
+ expect(result.args).toEqual({
71
+ nested: { key: "value" },
72
+ list: [1, 2, 3],
73
+ });
74
+ });
75
+ it("parses null values", () => {
76
+ const result = parseCommand('cmd {"key":null}');
77
+ expect(result.args).toEqual({ key: null });
78
+ });
79
+ });
80
+ describe("error handling", () => {
81
+ it("returns error for malformed JSON", () => {
82
+ const result = parseCommand('read_file {bad json}');
83
+ expect(result.error).toContain("Could not parse arguments as JSON");
84
+ expect(result.args).toEqual({});
85
+ expect(result.isHelp).toBe(false);
86
+ });
87
+ it("returns error for JSON array instead of object", () => {
88
+ const result = parseCommand('read_file ["a","b"]');
89
+ expect(result.error).toContain("Arguments must be a JSON object");
90
+ expect(result.args).toEqual({});
91
+ });
92
+ it("returns error for bare JSON primitive", () => {
93
+ const result = parseCommand('read_file "just a string"');
94
+ expect(result.error).toContain("Arguments must be a JSON object");
95
+ });
96
+ it("returns error for bare JSON number", () => {
97
+ const result = parseCommand("read_file 42");
98
+ expect(result.error).toContain("Arguments must be a JSON object");
99
+ });
100
+ it("returns error for incomplete JSON", () => {
101
+ const result = parseCommand('read_file {"path":"/tmp');
102
+ expect(result.error).toContain("Could not parse arguments as JSON");
103
+ });
104
+ });
105
+ describe("edge cases", () => {
106
+ it("handles subcommand with spaces in name (unusual but valid)", () => {
107
+ // First space splits subcommand from args
108
+ const result = parseCommand('my tool {"a":1}');
109
+ expect(result.subcommand).toBe("my");
110
+ expect(result.args).toEqual({});
111
+ expect(result.error).toContain("Could not parse arguments");
112
+ });
113
+ it("handles subcommand followed by empty args", () => {
114
+ const result = parseCommand("read_file ");
115
+ expect(result.subcommand).toBe("read_file");
116
+ expect(result.args).toEqual({});
117
+ expect(result.error).toBeNull();
118
+ });
119
+ it("handles subcommand followed by spaces", () => {
120
+ const result = parseCommand("read_file ");
121
+ expect(result.subcommand).toBe("read_file");
122
+ expect(result.args).toEqual({});
123
+ });
124
+ it("handles JSON with leading/trailing whitespace in args", () => {
125
+ const result = parseCommand('read_file {"path":"/tmp"} ');
126
+ expect(result.subcommand).toBe("read_file");
127
+ expect(result.args).toEqual({ path: "/tmp" });
128
+ });
129
+ it("handles empty JSON object", () => {
130
+ const result = parseCommand("cmd {}");
131
+ expect(result.subcommand).toBe("cmd");
132
+ expect(result.args).toEqual({});
133
+ expect(result.error).toBeNull();
134
+ });
135
+ it("handles subcommand containing underscores", () => {
136
+ const result = parseCommand('filesystem_read_file {"path":"/tmp"}');
137
+ expect(result.subcommand).toBe("filesystem_read_file");
138
+ expect(result.args).toEqual({ path: "/tmp" });
139
+ });
140
+ });
141
+ });
142
+ //# sourceMappingURL=parser.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.test.js","sourceRoot":"","sources":["../src/parser.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import type { DiscoveredServer } from "./discoverer.js";
3
+ /**
4
+ * Forward a tool call to an upstream MCP server and return the result.
5
+ */
6
+ export declare function forwardToolCall(server: DiscoveredServer, toolName: string, args: Record<string, unknown>): Promise<CallToolResult>;
package/dist/proxy.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Forward a tool call to an upstream MCP server and return the result.
3
+ */
4
+ export async function forwardToolCall(server, toolName, args) {
5
+ return server.client.callTool({
6
+ name: toolName,
7
+ arguments: args,
8
+ });
9
+ }
10
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAwB,EACxB,QAAgB,EAChB,IAA6B;IAE7B,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC5B,IAAI,EAAE,QAAQ;QACd,SAAS,EAAE,IAAI;KAChB,CAA4B,CAAC;AAChC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { forwardToolCall } from "./proxy.js";
3
+ describe("forwardToolCall", () => {
4
+ it("calls client.callTool with the correct tool name and arguments", async () => {
5
+ const callTool = vi.fn().mockResolvedValue({
6
+ content: [{ type: "text", text: "success" }],
7
+ });
8
+ const server = {
9
+ client: { callTool },
10
+ };
11
+ const result = await forwardToolCall(server, "read_file", {
12
+ path: "/tmp/test.txt",
13
+ });
14
+ expect(callTool).toHaveBeenCalledWith({
15
+ name: "read_file",
16
+ arguments: { path: "/tmp/test.txt" },
17
+ });
18
+ expect(result).toEqual({
19
+ content: [{ type: "text", text: "success" }],
20
+ });
21
+ });
22
+ it("passes through errors from the upstream server", async () => {
23
+ const callTool = vi
24
+ .fn()
25
+ .mockRejectedValue(new Error("Upstream connection failed"));
26
+ const server = {
27
+ client: { callTool },
28
+ };
29
+ await expect(forwardToolCall(server, "bad_tool", {})).rejects.toThrow("Upstream connection failed");
30
+ });
31
+ it("handles empty arguments", async () => {
32
+ const callTool = vi.fn().mockResolvedValue({
33
+ content: [{ type: "text", text: "ok" }],
34
+ });
35
+ const server = {
36
+ client: { callTool },
37
+ };
38
+ await forwardToolCall(server, "status", {});
39
+ expect(callTool).toHaveBeenCalledWith({
40
+ name: "status",
41
+ arguments: {},
42
+ });
43
+ });
44
+ it("handles nested arguments", async () => {
45
+ const callTool = vi.fn().mockResolvedValue({
46
+ content: [],
47
+ });
48
+ const server = {
49
+ client: { callTool },
50
+ };
51
+ await forwardToolCall(server, "complex", {
52
+ nested: { key: "value" },
53
+ array: [1, 2, 3],
54
+ });
55
+ expect(callTool).toHaveBeenCalledWith({
56
+ name: "complex",
57
+ arguments: { nested: { key: "value" }, array: [1, 2, 3] },
58
+ });
59
+ });
60
+ });
61
+ //# sourceMappingURL=proxy.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.test.js","sourceRoot":"","sources":["../src/proxy.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { MCPicoConfig } from "./config.js";
2
+ /**
3
+ * Start the MCPico proxy server.
4
+ *
5
+ * 1. Validate config
6
+ * 2. Connect to all upstream servers
7
+ * 3. Discover and group their tools
8
+ * 4. Register grouped tools on the MCPico server
9
+ * 5. Listen for client connections
10
+ */
11
+ export declare function startServer(config: MCPicoConfig): Promise<void>;