@mhingston5/conduit 1.1.1 → 1.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
@@ -75,7 +75,7 @@ upstreams:
75
75
 
76
76
  ### 3. Execute TypeScript
77
77
 
78
- Using any [MCP Client](https://modelcontextprotocol.io/clients) (Claude Desktop, etc.), call `mcp.executeTypeScript`:
78
+ Using any [MCP Client](https://modelcontextprotocol.io/clients) (Claude Desktop, etc.), call `mcp_execute_typescript`:
79
79
 
80
80
  ```ts
81
81
  // The agent writes this code:
@@ -182,7 +182,7 @@ To get the most out of Conduit, you should guide your LLM (agent) to prefer mult
182
182
  **Recommended System Prompt Additions:**
183
183
  > You are equipped with a "Code Mode" execution environment via Conduit.
184
184
  >
185
- > - **Prefer `mcp.executeTypeScript`**: Use this for any task requiring multiple steps, data transformation, or logical branching.
185
+ > - **Prefer `mcp_execute_typescript`**: Use this for any task requiring multiple steps, data transformation, or logical branching.
186
186
  > - **Use `tools.*` SDK**: Within your scripts, access all upstream tools via the `tools` namespace (e.g., `await tools.github.create_issue(...)`).
187
187
  > - **Avoid JSON tool-calling overhead**: Instead of making 5 separate tool calls and waiting for 5 round-trips, write one script that orchestrates the entire operation.
188
188
  > - **Data Transformation**: Perform loops, filters, and aggregations directly in your code rather than asking the user (or yourself) to process large datasets in the chat context.
@@ -12,7 +12,7 @@ Conduit is built with a modular architecture, designed to be secure, observable,
12
12
 
13
13
  ## Detailed Flow
14
14
 
15
- 1. **Client Request**: A client (like VS Code or Claude Desktop) sends a JSON-RPC request (`mcp.executeTypeScript`).
15
+ 1. **Client Request**: A client (like VS Code or Claude Desktop) sends a JSON-RPC request (`mcp_execute_typescript`).
16
16
  2. **Transportation**: The request is received via `SocketTransport` (TCP/UDS/Pipe).
17
17
  3. **Dispatch**: `RequestController` validates the request and session tokens.
18
18
  4. **Tool Discovery**: `GatewayService` aggregates tools from all upstream MCP servers.
package/docs/CODE_MODE.md CHANGED
@@ -28,6 +28,6 @@ Because logic executes in a sandbox, you can enforce limits on loops, memory, an
28
28
  ## Implementation in Conduit
29
29
 
30
30
  Conduit provides:
31
- 1. **`executeTypeScript` / `executePython`**: The entry points.
31
+ 1. **`mcp_execute_typescript` / `mcp_execute_python`**: The entry points.
32
32
  2. **`tools.*` SDK**: A dynamically generated client injected into the runtime.
33
33
  3. **Sandboxes**: Deno, Pyodide, and isolated-vm to run the code safely.
package/docs/SECURITY.md CHANGED
@@ -27,7 +27,7 @@ Conduit enforces strict Server-Side Request Forgery (SSRF) protections on upstre
27
27
  ## Authorization
28
28
 
29
29
  - **Master Token**: Full access to all methods (set via `IPC_BEARER_TOKEN`).
30
- - **Session Tokens**: Generated per-execution, restricted to `mcp.discoverTools` and `mcp.callTool` only.
30
+ - **Session Tokens**: Generated per-execution, restricted to `mcp_discover_tools` and `mcp_call_tool` only.
31
31
  - **Tool Allowlisting**: Per-request scope limits which tools code can discover/call (e.g., `["github.*"]`).
32
32
 
33
33
  ## Runtime Security
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhingston5/conduit",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "type": "module",
5
5
  "description": "A secure Code Mode execution substrate for MCP agents",
6
6
  "main": "index.js",
@@ -81,12 +81,12 @@ async function sendIPCRequest(method: string, params: any) {
81
81
 
82
82
  // Internal tool call function - used by generated SDK
83
83
  const __internalCallTool = async (name: string, params: any) => {
84
- return await sendIPCRequest('mcp.callTool', { name, arguments: params });
84
+ return await sendIPCRequest('mcp_call_tool', { name, arguments: params });
85
85
  };
86
86
 
87
87
  // Tool discovery - still available for dynamic scenarios
88
88
  (globalThis as any).discoverMCPTools = async (options: any) => {
89
- const result = await sendIPCRequest('mcp.discoverTools', options);
89
+ const result = await sendIPCRequest('mcp_discover_tools', options);
90
90
  return result.tools || [];
91
91
  };
92
92
 
@@ -31,7 +31,7 @@ export class AuthMiddleware implements Middleware {
31
31
 
32
32
  // Strict scoping for session tokens
33
33
  if (isSession) {
34
- const allowedMethods = ['initialize', 'notifications/initialized', 'mcp.discoverTools', 'mcp.callTool', 'ping'];
34
+ const allowedMethods = ['initialize', 'notifications/initialized', 'mcp_discover_tools', 'mcp_call_tool', 'ping', 'tools/list', 'tools/call'];
35
35
  if (!allowedMethods.includes(request.method)) {
36
36
  return {
37
37
  jsonrpc: '2.0',
@@ -102,23 +102,24 @@ export class RequestController {
102
102
 
103
103
  switch (method) {
104
104
  case 'tools/list': // Standard MCP method name
105
- case 'mcp.discoverTools':
105
+ case 'mcp_discover_tools':
106
106
  return this.handleDiscoverTools(params, context, id);
107
- case 'mcp.listToolPackages':
107
+ case 'mcp_list_tool_packages':
108
108
  return this.handleListToolPackages(params, context, id);
109
- case 'mcp.listToolStubs':
109
+ case 'mcp_list_tool_stubs':
110
110
  return this.handleListToolStubs(params, context, id);
111
- case 'mcp.readToolSchema':
111
+ case 'mcp_read_tool_schema':
112
112
  return this.handleReadToolSchema(params, context, id);
113
- case 'mcp.validateTool':
113
+ case 'mcp_validate_tool':
114
114
  return this.handleValidateTool(request, context);
115
- case 'mcp.callTool':
115
+ case 'mcp_call_tool':
116
+ case 'tools/call':
116
117
  return this.handleCallTool(params, context, id);
117
- case 'mcp.executeTypeScript':
118
+ case 'mcp_execute_typescript':
118
119
  return this.handleExecuteTypeScript(params, context, id);
119
- case 'mcp.executePython':
120
+ case 'mcp_execute_python':
120
121
  return this.handleExecutePython(params, context, id);
121
- case 'mcp.executeIsolate':
122
+ case 'mcp_execute_isolate':
122
123
  return this.handleExecuteIsolate(params, context, id);
123
124
  case 'initialize':
124
125
  return this.handleInitialize(params, context, id);
@@ -209,6 +210,17 @@ export class RequestController {
209
210
 
210
211
  private async handleCallTool(params: any, context: ExecutionContext, id: string | number): Promise<JSONRPCResponse> {
211
212
  const { name, arguments: toolArgs } = params;
213
+
214
+ // Route built-in tools to their specific handlers
215
+ switch (name) {
216
+ case 'mcp_execute_typescript':
217
+ return this.handleExecuteTypeScript(toolArgs, context, id);
218
+ case 'mcp_execute_python':
219
+ return this.handleExecutePython(toolArgs, context, id);
220
+ case 'mcp_execute_isolate':
221
+ return this.handleExecuteIsolate(toolArgs, context, id);
222
+ }
223
+
212
224
  const response = await this.gatewayService.callTool(name, toolArgs, context);
213
225
  return { ...response, id };
214
226
  }
@@ -131,11 +131,11 @@ async function handleTask(data: any) {
131
131
  };
132
132
 
133
133
  (p as any).globals.set('discover_mcp_tools_js', (options: any) => {
134
- return sendIPCRequest('mcp.discoverTools', options);
134
+ return sendIPCRequest('mcp_discover_tools', options);
135
135
  });
136
136
 
137
137
  (p as any).globals.set('call_mcp_tool_js', (name: string, args: any) => {
138
- return sendIPCRequest('mcp.callTool', { name, arguments: args });
138
+ return sendIPCRequest('mcp_call_tool', { name, arguments: args });
139
139
  });
140
140
 
141
141
  if (shim) {
@@ -12,7 +12,7 @@ import addFormats from 'ajv-formats';
12
12
 
13
13
  const BUILT_IN_TOOLS: ToolSchema[] = [
14
14
  {
15
- name: 'mcp.executeTypeScript',
15
+ name: 'mcp_execute_typescript',
16
16
  description: 'Executes TypeScript code in a secure sandbox with access to `tools.*` SDK.',
17
17
  inputSchema: {
18
18
  type: 'object',
@@ -31,7 +31,7 @@ const BUILT_IN_TOOLS: ToolSchema[] = [
31
31
  }
32
32
  },
33
33
  {
34
- name: 'mcp.executePython',
34
+ name: 'mcp_execute_python',
35
35
  description: 'Executes Python code in a secure sandbox with access to `tools.*` SDK.',
36
36
  inputSchema: {
37
37
  type: 'object',
@@ -50,7 +50,7 @@ const BUILT_IN_TOOLS: ToolSchema[] = [
50
50
  }
51
51
  },
52
52
  {
53
- name: 'mcp.executeIsolate',
53
+ name: 'mcp_execute_isolate',
54
54
  description: 'Executes JavaScript code in a high-speed V8 isolate (no Deno/Node APIs).',
55
55
  inputSchema: {
56
56
  type: 'object',
@@ -32,8 +32,8 @@ describe('Contract Test: Conduit vs Reference MCP', () => {
32
32
 
33
33
  it('should successfully discover tools from reference MCP', async () => {
34
34
  const tools = await gateway.discoverTools(context);
35
- expect(tools).toHaveLength(1);
36
- expect(tools[0].name).toBe('ref__echo');
35
+ expect(tools.length).toBeGreaterThanOrEqual(1);
36
+ expect(tools.find(t => t.name === 'ref__echo')).toBeDefined();
37
37
  });
38
38
 
39
39
  it('should successfully call tool on reference MCP', async () => {
@@ -96,7 +96,7 @@ describe('Dynamic Tool Calling (E2E)', () => {
96
96
  const response = await requestController.handleRequest({
97
97
  jsonrpc: '2.0',
98
98
  id: 1,
99
- method: 'mcp.executeTypeScript',
99
+ method: 'mcp_execute_typescript',
100
100
  params: { code },
101
101
  auth: { bearerToken: testToken }
102
102
  }, context);
@@ -122,7 +122,7 @@ print(f"RESULT:{result}")
122
122
  const response = await requestController.handleRequest({
123
123
  jsonrpc: '2.0',
124
124
  id: 2,
125
- method: 'mcp.executePython',
125
+ method: 'mcp_execute_python',
126
126
  params: { code },
127
127
  auth: { bearerToken: testToken }
128
128
  }, context);
@@ -151,7 +151,7 @@ print(f"RESULT:{result}")
151
151
  const response = await requestController.handleRequest({
152
152
  jsonrpc: '2.0',
153
153
  id: 3,
154
- method: 'mcp.executeTypeScript',
154
+ method: 'mcp_execute_typescript',
155
155
  params: {
156
156
  code,
157
157
  allowedTools: ['mock.hello'] // Only mock.hello allowed
@@ -178,7 +178,7 @@ print(f"RESULT:{result}")
178
178
  const response = await requestController.handleRequest({
179
179
  jsonrpc: '2.0',
180
180
  id: 4,
181
- method: 'mcp.executeTypeScript',
181
+ method: 'mcp_execute_typescript',
182
182
  params: {
183
183
  code,
184
184
  allowedTools: ['mock.*'] // Wildcard allows all mock tools
@@ -206,7 +206,7 @@ print(f"RESULT:{result}")
206
206
  const response = await requestController.handleRequest({
207
207
  jsonrpc: '2.0',
208
208
  id: 5,
209
- method: 'mcp.executeIsolate',
209
+ method: 'mcp_execute_isolate',
210
210
  params: {
211
211
  code,
212
212
  allowedTools: ['mock.*'],
@@ -149,7 +149,7 @@ describe('E2E: Stdio Upstream Integration', () => {
149
149
  const response = await sendRequest({
150
150
  jsonrpc: '2.0',
151
151
  id: '1',
152
- method: 'mcp.discoverTools',
152
+ method: 'mcp_discover_tools',
153
153
  params: {},
154
154
  auth: { bearerToken: ipcToken },
155
155
  });
@@ -169,7 +169,7 @@ describe('E2E: Stdio Upstream Integration', () => {
169
169
  const discoverResponse = await sendRequest({
170
170
  jsonrpc: '2.0',
171
171
  id: '1',
172
- method: 'mcp.discoverTools',
172
+ method: 'mcp_discover_tools',
173
173
  params: {},
174
174
  auth: { bearerToken: ipcToken },
175
175
  });
@@ -181,7 +181,7 @@ describe('E2E: Stdio Upstream Integration', () => {
181
181
  const callResponse = await sendRequest({
182
182
  jsonrpc: '2.0',
183
183
  id: '2',
184
- method: 'mcp.callTool',
184
+ method: 'mcp_call_tool',
185
185
  params: {
186
186
  name: echoTool.name,
187
187
  arguments: { message: 'Hello from E2E test!' },
@@ -32,17 +32,17 @@ describe('GatewayService', () => {
32
32
 
33
33
  const tools = await gateway.discoverTools(context);
34
34
  expect(tools.length).toBeGreaterThanOrEqual(3);
35
- expect(tools.find(t => t.name === 'mcp.executeTypeScript')).toBeDefined();
36
- expect(tools.find(t => t.name === 'mcp.executePython')).toBeDefined();
37
- expect(tools.find(t => t.name === 'mcp.executeIsolate')).toBeDefined();
35
+ expect(tools.find(t => t.name === 'mcp_execute_typescript')).toBeDefined();
36
+ expect(tools.find(t => t.name === 'mcp_execute_python')).toBeDefined();
37
+ expect(tools.find(t => t.name === 'mcp_execute_isolate')).toBeDefined();
38
38
  expect(tools.find(t => t.name === 'u1__t1')).toBeDefined();
39
39
  expect(tools.find(t => t.name === 'u2__t2')).toBeDefined();
40
40
  });
41
41
 
42
42
  it('should return schema for built-in tools', async () => {
43
- const schema = await gateway.getToolSchema('mcp.executeTypeScript', context);
43
+ const schema = await gateway.getToolSchema('mcp_execute_typescript', context);
44
44
  expect(schema).toBeDefined();
45
- expect(schema?.name).toBe('mcp.executeTypeScript');
45
+ expect(schema?.name).toBe('mcp_execute_typescript');
46
46
  expect(schema?.inputSchema.required).toContain('code');
47
47
  });
48
48
 
@@ -92,7 +92,7 @@ describe('V1 Hardening Tests', () => {
92
92
  const response = await sendRequest({
93
93
  jsonrpc: '2.0',
94
94
  id: 1,
95
- method: 'mcp.executeTypeScript',
95
+ method: 'mcp_execute_typescript',
96
96
  params: { code: 'console.log("hi")' },
97
97
  auth: { bearerToken: sessionToken }
98
98
  });
@@ -114,7 +114,7 @@ describe('V1 Hardening Tests', () => {
114
114
  const response = await sendRequest({
115
115
  jsonrpc: '2.0',
116
116
  id: 2,
117
- method: 'mcp.discoverTools',
117
+ method: 'mcp_discover_tools',
118
118
  params: {},
119
119
  auth: { bearerToken: sessionToken }
120
120
  });
@@ -127,7 +127,7 @@ describe('V1 Hardening Tests', () => {
127
127
  const response = await sendRequest({
128
128
  jsonrpc: '2.0',
129
129
  id: 3,
130
- method: 'mcp.executeTypeScript',
130
+ method: 'mcp_execute_typescript',
131
131
  params: { code: 'import * as os from "os"; console.log("hi")' },
132
132
  auth: { bearerToken: 'master-token' }
133
133
  });
@@ -84,7 +84,7 @@ describe('RequestController Routing', () => {
84
84
  const result = await controller.handleRequest({
85
85
  jsonrpc: '2.0',
86
86
  id: 1,
87
- method: 'mcp.executeTypeScript',
87
+ method: 'mcp_execute_typescript',
88
88
  params: {
89
89
  code: 'console.log("simple")',
90
90
  limits: {}
@@ -101,7 +101,7 @@ describe('RequestController Routing', () => {
101
101
  const result = await controller.handleRequest({
102
102
  jsonrpc: '2.0',
103
103
  id: 1,
104
- method: 'mcp.executeTypeScript',
104
+ method: 'mcp_execute_typescript',
105
105
  params: {
106
106
  code: 'import { foo } from "bar"; console.log(foo)',
107
107
  limits: {}
@@ -118,7 +118,7 @@ describe('RequestController Routing', () => {
118
118
  const result = await controller.handleRequest({
119
119
  jsonrpc: '2.0',
120
120
  id: 1,
121
- method: 'mcp.executeTypeScript',
121
+ method: 'mcp_execute_typescript',
122
122
  params: {
123
123
  code: 'export const foo = "bar"',
124
124
  limits: {}
@@ -134,7 +134,7 @@ describe('RequestController Routing', () => {
134
134
  const result = await controller.handleRequest({
135
135
  jsonrpc: '2.0',
136
136
  id: 1,
137
- method: 'mcp.executeTypeScript',
137
+ method: 'mcp_execute_typescript',
138
138
  params: {
139
139
  code: 'console.log(Deno.version)',
140
140
  limits: {}
@@ -145,4 +145,24 @@ describe('RequestController Routing', () => {
145
145
  expect(mockDenoExecutor.execute).toHaveBeenCalled();
146
146
  expect(mockIsolateExecutor.execute).not.toHaveBeenCalled();
147
147
  });
148
+
149
+ it('should route tools/call for built-in tools', async () => {
150
+ const result = await controller.handleRequest({
151
+ jsonrpc: '2.0',
152
+ id: 1,
153
+ method: 'tools/call',
154
+ params: {
155
+ name: 'mcp_execute_typescript',
156
+ arguments: {
157
+ code: 'console.log("via tools/call")',
158
+ limits: {}
159
+ }
160
+ },
161
+ auth: { bearerToken: 'master-token' }
162
+ }, mockContext);
163
+
164
+ expect(mockIsolateExecutor.execute).toHaveBeenCalled();
165
+ expect(result!.result.stdout).toBe('isolate');
166
+ });
167
+
148
168
  });
@@ -100,7 +100,7 @@ describe('SocketTransport', () => {
100
100
  });
101
101
  }
102
102
 
103
- it('should handle mcp.executeTypeScript request', async () => {
103
+ it('should handle mcp_execute_typescript request', async () => {
104
104
  transport = new SocketTransport(logger, requestController, concurrencyService);
105
105
  const address = await transport.listen({ port: 0 });
106
106
  const portMatch = address.match(/:(\d+)$/);
@@ -112,7 +112,7 @@ describe('SocketTransport', () => {
112
112
  const request = {
113
113
  jsonrpc: '2.0',
114
114
  id: 1,
115
- method: 'mcp.executeTypeScript',
115
+ method: 'mcp_execute_typescript',
116
116
  params: { code: 'console.log("hello E2E")' },
117
117
  auth: { bearerToken: testToken }
118
118
  };
@@ -153,7 +153,7 @@ describe('SocketTransport', () => {
153
153
  const request = {
154
154
  jsonrpc: '2.0',
155
155
  id: 12345,
156
- method: 'mcp.executeTypeScript',
156
+ method: 'mcp_execute_typescript',
157
157
  params: { code: 'console.log("hello")' },
158
158
  auth: { bearerToken: testToken }
159
159
  };
@@ -24,7 +24,7 @@ describe('E2E: Native Stdio Mode', () => {
24
24
  const request = {
25
25
  jsonrpc: '2.0',
26
26
  id: '1',
27
- method: 'mcp.discoverTools',
27
+ method: 'mcp_discover_tools',
28
28
  params: {},
29
29
  // Use a dummy token, security service might reject if auth is enabled but
30
30
  // the default config generates a random token.