@toolplex/client 0.1.18 → 0.1.20

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 (41) hide show
  1. package/dist/mcp-server/index.js +12 -0
  2. package/dist/mcp-server/registry.d.ts +17 -0
  3. package/dist/mcp-server/registry.js +23 -0
  4. package/dist/mcp-server/toolHandlers/callToolHandler.js +1 -0
  5. package/dist/mcp-server/toolHandlers/initHandler.d.ts +9 -0
  6. package/dist/mcp-server/toolHandlers/initHandler.js +50 -1
  7. package/dist/mcp-server/toolHandlers/lookupEntityHandler.js +1 -0
  8. package/dist/mcp-server/toolHandlers/searchHandler.js +1 -1
  9. package/dist/mcp-server/toolplexServer.js +5 -0
  10. package/dist/mcp-server/utils/runtimeCheck.d.ts +18 -0
  11. package/dist/mcp-server/utils/runtimeCheck.js +70 -6
  12. package/dist/server-manager/serverManager.js +3 -9
  13. package/dist/shared/enhancedPath.js +3 -3
  14. package/dist/shared/mcpServerTypes.d.ts +14 -0
  15. package/dist/src/mcp-server/clientContext.js +118 -0
  16. package/dist/src/mcp-server/logging/telemetryLogger.js +54 -0
  17. package/dist/src/mcp-server/policy/callToolObserver.js +27 -0
  18. package/dist/src/mcp-server/policy/feedbackPolicy.js +39 -0
  19. package/dist/src/mcp-server/policy/installObserver.js +35 -0
  20. package/dist/src/mcp-server/policy/playbookPolicy.js +81 -0
  21. package/dist/src/mcp-server/policy/policyEnforcer.js +105 -0
  22. package/dist/src/mcp-server/policy/serverPolicy.js +63 -0
  23. package/dist/src/mcp-server/promptsCache.js +51 -0
  24. package/dist/src/mcp-server/registry.js +134 -0
  25. package/dist/src/mcp-server/serversCache.js +100 -0
  26. package/dist/src/mcp-server/toolDefinitionsCache.js +67 -0
  27. package/dist/src/mcp-server/toolHandlers/initHandler.js +189 -0
  28. package/dist/src/mcp-server/toolplexApi/service.js +221 -0
  29. package/dist/src/mcp-server/toolplexApi/types.js +1 -0
  30. package/dist/src/mcp-server/utils/initServerManagers.js +31 -0
  31. package/dist/src/mcp-server/utils/runtimeCheck.js +94 -0
  32. package/dist/src/shared/enhancedPath.js +52 -0
  33. package/dist/src/shared/fileLogger.js +66 -0
  34. package/dist/src/shared/mcpServerTypes.js +158 -0
  35. package/dist/src/shared/serverManagerTypes.js +73 -0
  36. package/dist/src/shared/stdioServerManagerClient.js +98 -0
  37. package/dist/tests/unit/bundledDependencies.test.js +152 -0
  38. package/dist/tests/unit/registry.test.js +216 -0
  39. package/dist/version.d.ts +1 -1
  40. package/dist/version.js +1 -1
  41. package/package.json +8 -3
@@ -0,0 +1,158 @@
1
+ // src/types/types.ts
2
+ import { z } from "zod";
3
+ // --------------------
4
+ // Enums
5
+ // --------------------
6
+ export const TransportTypeSchema = z.enum(["stdio", "sse"]);
7
+ export const RuntimeSchema = z.enum(["node", "python", "go", "docker"]);
8
+ // --------------------
9
+ // LLMContext
10
+ // --------------------
11
+ export const LLMContextSchema = z
12
+ .object({
13
+ model_family: z.string(),
14
+ model_name: z.string().min(1),
15
+ model_version: z.string(),
16
+ chat_client: z.string().optional(),
17
+ })
18
+ .strict();
19
+ // --------------------
20
+ // ServerConfig
21
+ // --------------------
22
+ export const ServerConfigSchema = z.object({
23
+ server_name: z.string().optional(),
24
+ description: z.string().optional(),
25
+ command: z.string().optional(),
26
+ args: z.array(z.string()).optional(),
27
+ runtime: RuntimeSchema.optional(),
28
+ env: z.record(z.string()).optional(),
29
+ url: z.string().optional(),
30
+ transport: TransportTypeSchema,
31
+ });
32
+ // --------------------
33
+ // InitializeToolplexParams
34
+ // --------------------
35
+ export const InitializeToolplexParamsSchema = z.object({
36
+ llm_context: LLMContextSchema,
37
+ });
38
+ // --------------------
39
+ // SearchParams
40
+ // --------------------
41
+ export const SearchParamsSchema = z.object({
42
+ query: z.string(),
43
+ expanded_keywords: z.array(z.string()).optional(),
44
+ filter: z.enum(["all", "servers_only", "playbooks_only"]).optional(),
45
+ size: z.number().int().min(1).max(25).optional(),
46
+ scope: z.enum(["all", "public_only", "private_only"]).optional(),
47
+ });
48
+ // --------------------
49
+ // LookupEntityParams
50
+ // --------------------
51
+ export const LookupEntityParamsSchema = z.object({
52
+ entity_type: z.enum(["server", "playbook", "feedback"]),
53
+ entity_id: z.string(),
54
+ });
55
+ // --------------------
56
+ // InstallParams
57
+ // --------------------
58
+ export const InstallParamsSchema = z.object({
59
+ server_id: z.string(),
60
+ server_name: z.string(),
61
+ description: z.string(),
62
+ config: ServerConfigSchema,
63
+ });
64
+ // --------------------
65
+ // ListToolsParams
66
+ // --------------------
67
+ export const ListToolsParamsSchema = z.object({
68
+ server_id: z.string().optional(),
69
+ });
70
+ // --------------------
71
+ // GetServerConfigParams
72
+ // --------------------
73
+ export const GetServerConfigParamsSchema = z.object({
74
+ server_id: z.string().optional(),
75
+ });
76
+ // --------------------
77
+ // CallToolParams
78
+ // --------------------
79
+ export const CallToolParamsSchema = z.object({
80
+ server_id: z.string(),
81
+ tool_name: z.string(),
82
+ arguments: z.record(z.any()),
83
+ });
84
+ // --------------------
85
+ // UninstallParams
86
+ // --------------------
87
+ export const UninstallParamsSchema = z.object({
88
+ server_id: z.string(),
89
+ });
90
+ // --------------------
91
+ // PlaybookAction (shared type for actions)
92
+ // --------------------
93
+ export const PlaybookActionSchema = z.object({
94
+ do: z.string(),
95
+ call: z.string().optional(),
96
+ args: z
97
+ .record(z.object({
98
+ type: z.enum([
99
+ "string",
100
+ "number",
101
+ "boolean",
102
+ "array",
103
+ "object",
104
+ "placeholder",
105
+ ]),
106
+ example: z.any(),
107
+ }))
108
+ .optional(),
109
+ });
110
+ // --------------------
111
+ // SavePlaybookParams
112
+ // --------------------
113
+ export const SavePlaybookParamsSchema = z.object({
114
+ playbook_name: z.string(),
115
+ description: z.string(),
116
+ // Requires at least one action to have a "call" property
117
+ actions: z
118
+ .array(PlaybookActionSchema)
119
+ .refine((actions) => actions.some((action) => typeof action.call === "string" && action.call.length > 0), {
120
+ message: 'At least one action must include a "call" property',
121
+ path: ["actions"],
122
+ }),
123
+ domain: z.string().optional(),
124
+ keywords: z.array(z.string()).optional(),
125
+ requirements: z.array(z.string()).optional(),
126
+ privacy: z.enum(["public", "private"]).optional(),
127
+ source_playbook_id: z.string().optional(),
128
+ fork_reason: z.string().optional(),
129
+ });
130
+ // --------------------
131
+ // LogPlaybookUsageParams
132
+ // --------------------
133
+ export const LogPlaybookUsageParamsSchema = z.object({
134
+ playbook_id: z.string(),
135
+ success: z.boolean(),
136
+ error_message: z.string().optional(),
137
+ });
138
+ // --------------------
139
+ // SubmitFeedbackParams
140
+ // --------------------
141
+ export const SubmitFeedbackParamsSchema = z.object({
142
+ target_type: z.enum(["server", "playbook"]),
143
+ target_id: z.string(),
144
+ vote: z.enum(["up", "down"]),
145
+ message: z.string().optional(),
146
+ security_assessment: z
147
+ .object({
148
+ security_flags: z.array(z.union([
149
+ z.string(),
150
+ z.object({
151
+ custom_flag: z.string(),
152
+ }),
153
+ ])),
154
+ risk_assessment: z.string(),
155
+ context_note: z.string().optional(),
156
+ })
157
+ .optional(),
158
+ });
@@ -0,0 +1,73 @@
1
+ import { z } from "zod";
2
+ // --------------------
3
+ // initialize
4
+ // --------------------
5
+ export const InitializeResultSchema = z.object({
6
+ succeeded: z.array(z.object({
7
+ server_id: z.string(),
8
+ server_name: z.string(),
9
+ description: z.string(),
10
+ })),
11
+ failures: z.record(z.object({
12
+ server_id: z.string(),
13
+ server_name: z.string(),
14
+ error: z.string(),
15
+ })),
16
+ });
17
+ // --------------------
18
+ // Install
19
+ // --------------------
20
+ export const ServerInstallResultSchema = z.object({
21
+ server_id: z.string(),
22
+ server_name: z.string(),
23
+ });
24
+ // --------------------
25
+ // Uninstall
26
+ // --------------------
27
+ export const ServerUninstallResultSchema = z.object({
28
+ server_id: z.string(),
29
+ server_name: z.string(),
30
+ });
31
+ // --------------------
32
+ // list_servers
33
+ // --------------------
34
+ export const ListServersResultSchema = z.object({
35
+ servers: z.array(z.object({
36
+ server_id: z.string(),
37
+ server_name: z.string(),
38
+ description: z.string(),
39
+ tool_count: z.number(),
40
+ })),
41
+ });
42
+ // --------------------
43
+ // list_tools
44
+ // --------------------
45
+ export const ListToolsResultSchema = z.object({
46
+ server_id: z.string(),
47
+ server_name: z.string(),
48
+ tools: z.array(z.object({
49
+ name: z.string(),
50
+ description: z.string().optional(),
51
+ inputSchema: z.any(),
52
+ })),
53
+ });
54
+ // --------------------
55
+ // list_all_tools
56
+ // --------------------
57
+ export const ListAllToolsResultSchema = z.object({
58
+ tools: z.record(z.array(z.object({
59
+ name: z.string(),
60
+ description: z.string().optional(),
61
+ inputSchema: z.any(),
62
+ }))),
63
+ });
64
+ // --------------------
65
+ // call_tool
66
+ // --------------------
67
+ export const CallToolResultSchema = z.object({
68
+ result: z.any(),
69
+ });
70
+ // --------------------
71
+ // cleanup
72
+ // --------------------
73
+ export const CleanupResultSchema = z.object({});
@@ -0,0 +1,98 @@
1
+ import { spawn } from "child_process";
2
+ export class StdioServerManagerClient {
3
+ constructor(command, args, env) {
4
+ this.serverProcess = null;
5
+ this.messageHandlers = new Map();
6
+ this.messageBuffer = "";
7
+ this.command = command;
8
+ this.args = args;
9
+ this.env = env ? { ...process.env, ...env } : process.env;
10
+ }
11
+ async start() {
12
+ this.serverProcess = spawn(this.command, this.args, {
13
+ stdio: ["pipe", "pipe", "pipe"],
14
+ env: this.env,
15
+ });
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ this.serverProcess.on("error", (err) => {
18
+ process.stderr.write(`Server process error: ${err}\n`);
19
+ });
20
+ if (!this.serverProcess.stderr ||
21
+ !this.serverProcess.stdout ||
22
+ !this.serverProcess.stdin) {
23
+ throw new Error("Server process streams not available");
24
+ }
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ this.serverProcess.stderr.on("data", (data) => {
27
+ process.stderr.write(data);
28
+ });
29
+ this.serverProcess.stdout.on("data", (data) => {
30
+ this.messageBuffer += data.toString();
31
+ const lines = this.messageBuffer.split("\n");
32
+ this.messageBuffer = lines.pop() || "";
33
+ for (const line of lines) {
34
+ if (!line.trim())
35
+ continue;
36
+ try {
37
+ const message = JSON.parse(line.trim());
38
+ const handler = this.messageHandlers.get(message.id);
39
+ if (handler) {
40
+ handler(message);
41
+ this.messageHandlers.delete(message.id);
42
+ }
43
+ }
44
+ catch (e) {
45
+ process.stderr.write(`Raw line: ${line}\n`);
46
+ process.stderr.write(`Parse error: ${e}\n`);
47
+ }
48
+ }
49
+ });
50
+ this.serverProcess.on("exit", () => {
51
+ for (const [, h] of this.messageHandlers)
52
+ h({ __error__: { message: "Subprocess exited" } });
53
+ this.messageHandlers.clear();
54
+ this.serverProcess = null;
55
+ this.messageBuffer = "";
56
+ });
57
+ }
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ async sendRequest(method, params) {
60
+ return new Promise((resolve, reject) => {
61
+ const id = Date.now();
62
+ if (!this.serverProcess?.stdin) {
63
+ reject(Error("Server process not started"));
64
+ }
65
+ else {
66
+ this.messageHandlers.set(id, (frame) => {
67
+ if (frame.error || frame.__error__) {
68
+ const msg = frame.error?.message ?? frame.__error__?.message ?? "MCP error";
69
+ reject(new Error(msg));
70
+ }
71
+ else {
72
+ resolve(frame.result);
73
+ }
74
+ });
75
+ this.serverProcess.stdin.write(JSON.stringify({
76
+ jsonrpc: "2.0",
77
+ method,
78
+ params,
79
+ id,
80
+ }) + "\n");
81
+ }
82
+ setTimeout(() => {
83
+ if (this.messageHandlers.has(id)) {
84
+ this.messageHandlers.delete(id);
85
+ reject(new Error(`Request timed out: ${method}`));
86
+ }
87
+ }, 60000);
88
+ });
89
+ }
90
+ async stop() {
91
+ if (this.serverProcess) {
92
+ this.serverProcess.kill();
93
+ this.serverProcess = null;
94
+ this.messageHandlers.clear();
95
+ this.messageBuffer = "";
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Unit tests for Bundled Dependencies feature
3
+ *
4
+ * Tests both RuntimeCheck.resolveDependency() and initHandler.resolveDependencyForInit()
5
+ * which implement the bundled > system > error priority logic.
6
+ *
7
+ * Note: These tests use the real fs and which modules but control the test environment
8
+ * through Registry state and test file fixtures.
9
+ */
10
+ import { describe, it, beforeEach } from "node:test";
11
+ import assert from "node:assert";
12
+ import { RuntimeCheck } from "../../src/mcp-server/utils/runtimeCheck.js";
13
+ import { resolveDependencyForInit } from "../../src/mcp-server/toolHandlers/initHandler.js";
14
+ import Registry from "../../src/mcp-server/registry.js";
15
+ describe("Bundled Dependencies", () => {
16
+ beforeEach(() => {
17
+ // Reset Registry before each test
18
+ Registry.reset();
19
+ });
20
+ describe("RuntimeCheck.resolveDependency", () => {
21
+ describe("Priority 2: System PATH Fallback (using real system)", () => {
22
+ it("should resolve node from system PATH when no bundled deps", () => {
23
+ Registry.setBundledDependencies({});
24
+ // node should be available in system PATH
25
+ const resolved = RuntimeCheck.resolveDependency("node");
26
+ assert.ok(resolved.includes("node"));
27
+ assert.ok(resolved.length > 0);
28
+ });
29
+ it("should handle non-bundled dependencies", () => {
30
+ Registry.setBundledDependencies({ node: "/bundled/node" });
31
+ // git should fallback to system PATH (assuming git is installed)
32
+ try {
33
+ const resolved = RuntimeCheck.resolveDependency("git");
34
+ assert.ok(resolved.includes("git"));
35
+ }
36
+ catch (err) {
37
+ // If git is not installed, that's okay - this test just verifies the logic
38
+ assert.ok(err.message.includes("git"));
39
+ }
40
+ });
41
+ });
42
+ describe("Priority 3: Error When Neither Available", () => {
43
+ it("should throw error with hints for unknown commands", () => {
44
+ Registry.setBundledDependencies({});
45
+ assert.throws(() => RuntimeCheck.resolveDependency("definitely_not_a_real_command_12345"), /Command.*not found/);
46
+ });
47
+ it("should include install hints for known commands", () => {
48
+ Registry.setBundledDependencies({});
49
+ try {
50
+ // Try to resolve uvx which likely doesn't exist
51
+ RuntimeCheck.resolveDependency("uvx");
52
+ }
53
+ catch (err) {
54
+ // If it throws, it should have a helpful message
55
+ assert.ok(err.message.includes("uvx") ||
56
+ err.message.includes("Install") ||
57
+ err.message.includes("not found"));
58
+ }
59
+ });
60
+ });
61
+ });
62
+ describe("resolveDependencyForInit", () => {
63
+ describe("Priority 2: System PATH Fallback (using real system)", () => {
64
+ it("should return system path when bundled not available", () => {
65
+ const result = resolveDependencyForInit(undefined, "node");
66
+ // Should find node in system PATH and mark it as (system)
67
+ assert.ok(result.includes("(system)"));
68
+ assert.ok(result.includes("node"));
69
+ });
70
+ it("should handle undefined bundled path", () => {
71
+ const result = resolveDependencyForInit(undefined, "node");
72
+ // Should fallback gracefully
73
+ assert.ok(typeof result === "string");
74
+ assert.ok(result.length > 0);
75
+ });
76
+ });
77
+ describe("Priority 3: Not Available", () => {
78
+ it("should return 'not available' for non-existent commands", () => {
79
+ const result = resolveDependencyForInit(undefined, "definitely_not_a_real_command_12345");
80
+ assert.strictEqual(result, "not available");
81
+ });
82
+ it("should return 'not available' for non-existent bundled path", () => {
83
+ const result = resolveDependencyForInit("/this/path/definitely/does/not/exist/node", "node");
84
+ // Since bundled path doesn't exist, should either find system or return not available
85
+ assert.ok(result.includes("(system)") || result === "not available");
86
+ });
87
+ });
88
+ });
89
+ describe("RuntimeCheck Utilities", () => {
90
+ describe("extractCommandName", () => {
91
+ it("should extract first word as command name", () => {
92
+ assert.strictEqual(RuntimeCheck.extractCommandName("node"), "node");
93
+ assert.strictEqual(RuntimeCheck.extractCommandName("node --version"), "node");
94
+ assert.strictEqual(RuntimeCheck.extractCommandName("npx -y @some/package"), "npx");
95
+ });
96
+ it("should handle leading/trailing spaces", () => {
97
+ assert.strictEqual(RuntimeCheck.extractCommandName(" node "), "node");
98
+ assert.strictEqual(RuntimeCheck.extractCommandName(" python3 -m module "), "python3");
99
+ });
100
+ it("should handle empty strings", () => {
101
+ assert.strictEqual(RuntimeCheck.extractCommandName(""), "");
102
+ });
103
+ });
104
+ describe("validateCommandOrThrow", () => {
105
+ it("should not throw for available commands like node", () => {
106
+ // node should be available in system PATH
107
+ assert.doesNotThrow(() => {
108
+ RuntimeCheck.validateCommandOrThrow("node");
109
+ });
110
+ });
111
+ it("should throw for unavailable commands", () => {
112
+ assert.throws(() => {
113
+ RuntimeCheck.validateCommandOrThrow("definitely_not_a_real_command_12345");
114
+ });
115
+ });
116
+ it("should extract command name from full command string", () => {
117
+ // Should extract 'node' and validate it exists
118
+ assert.doesNotThrow(() => {
119
+ RuntimeCheck.validateCommandOrThrow("node --version");
120
+ });
121
+ });
122
+ });
123
+ });
124
+ describe("Registry Integration", () => {
125
+ it("should return copy of bundled dependencies", () => {
126
+ const deps = {
127
+ node: "/bundled/node",
128
+ python: "/bundled/python3",
129
+ };
130
+ Registry.setBundledDependencies(deps);
131
+ const retrieved = Registry.getBundledDependencies();
132
+ // Should be equal but not the same reference
133
+ assert.deepStrictEqual(retrieved, deps);
134
+ assert.notStrictEqual(retrieved, deps);
135
+ });
136
+ it("should handle bundled dependency path lookups", () => {
137
+ Registry.setBundledDependencies({
138
+ node: "/bundled/node",
139
+ python: "/bundled/python3",
140
+ });
141
+ assert.strictEqual(Registry.getBundledDependencyPath("node"), "/bundled/node");
142
+ assert.strictEqual(Registry.getBundledDependencyPath("python"), "/bundled/python3");
143
+ assert.strictEqual(Registry.getBundledDependencyPath("git"), undefined);
144
+ });
145
+ it("should reset bundled dependencies", () => {
146
+ Registry.setBundledDependencies({ node: "/bundled/node" });
147
+ Registry.reset();
148
+ const deps = Registry.getBundledDependencies();
149
+ assert.deepStrictEqual(deps, {});
150
+ });
151
+ });
152
+ });