@ruifung/codemode-bridge 1.0.6 → 1.0.7
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/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +2 -2
- package/dist/cli/index.js +2 -1
- package/dist/executor/container-executor.js +5 -0
- package/dist/mcp/executor.d.ts +2 -1
- package/dist/mcp/executor.js +36 -14
- package/dist/mcp/server.d.ts +3 -2
- package/dist/mcp/server.js +2 -2
- package/package.json +1 -1
package/dist/cli/commands.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* Run the bridge server
|
|
8
8
|
*/
|
|
9
|
-
export declare function runServer(configPath?: string, servers?: string[], debug?: boolean): Promise<void>;
|
|
9
|
+
export declare function runServer(configPath?: string, servers?: string[], debug?: boolean, executor?: string): Promise<void>;
|
|
10
10
|
/**
|
|
11
11
|
* List all configured servers
|
|
12
12
|
*/
|
package/dist/cli/commands.js
CHANGED
|
@@ -15,7 +15,7 @@ import { MCPClient } from "../mcp/mcp-client.js";
|
|
|
15
15
|
/**
|
|
16
16
|
* Run the bridge server
|
|
17
17
|
*/
|
|
18
|
-
export async function runServer(configPath, servers, debug) {
|
|
18
|
+
export async function runServer(configPath, servers, debug, executor) {
|
|
19
19
|
try {
|
|
20
20
|
// Initialize logger with debug mode if requested
|
|
21
21
|
initializeLogger(debug);
|
|
@@ -53,7 +53,7 @@ export async function runServer(configPath, servers, debug) {
|
|
|
53
53
|
}
|
|
54
54
|
logInfo(`Starting bridge with ${serverConfigs.length} server(s)`, { component: 'CLI' });
|
|
55
55
|
// Start the MCP bridge server
|
|
56
|
-
await startCodeModeBridgeServer(serverConfigs);
|
|
56
|
+
await startCodeModeBridgeServer(serverConfigs, executor);
|
|
57
57
|
// Flush buffered stderr output from stdio tools now that Bridge is fully running
|
|
58
58
|
flushStderrBuffer();
|
|
59
59
|
logInfo("Bridge is running!", { component: 'CLI' });
|
package/dist/cli/index.js
CHANGED
|
@@ -34,9 +34,10 @@ program
|
|
|
34
34
|
.option("-c, --config <path>", `Path to mcp.json configuration file (default: ${defaultConfigPath})`)
|
|
35
35
|
.option("-s, --servers <names>", "Comma-separated list of servers to connect to")
|
|
36
36
|
.option("-d, --debug", "Enable debug logging")
|
|
37
|
+
.option("-e, --executor <type>", "Executor type (isolated-vm, container, vm2)")
|
|
37
38
|
.action(async (options) => {
|
|
38
39
|
const servers = options.servers ? options.servers.split(",").map((s) => s.trim()) : undefined;
|
|
39
|
-
await runServer(options.config, servers, options.debug);
|
|
40
|
+
await runServer(options.config, servers, options.debug, options.executor);
|
|
40
41
|
});
|
|
41
42
|
// Config command group
|
|
42
43
|
const config = program.command("config").description("Manage bridge configuration").enablePositionalOptions();
|
|
@@ -71,6 +71,11 @@ export class ContainerExecutor {
|
|
|
71
71
|
this.memoryLimit = options.memoryLimit ?? '256m';
|
|
72
72
|
this.cpuLimit = options.cpuLimit ?? 1.0;
|
|
73
73
|
this.runtime = detectRuntime(options.runtime);
|
|
74
|
+
// Start initialization immediately to create the container before the first execution.
|
|
75
|
+
// Errors are caught and logged, but will also be thrown when execute() awaits this.init().
|
|
76
|
+
this.init().catch(err => {
|
|
77
|
+
process.stderr.write(`[container-executor] Immediate initialization failed: ${err.message}\n`);
|
|
78
|
+
});
|
|
74
79
|
}
|
|
75
80
|
/**
|
|
76
81
|
* Start the container and wait for the runner to signal readiness.
|
package/dist/mcp/executor.d.ts
CHANGED
|
@@ -19,13 +19,14 @@ export interface ExecutorInfo {
|
|
|
19
19
|
* Factory function to create an Executor instance.
|
|
20
20
|
*
|
|
21
21
|
* Selection logic:
|
|
22
|
+
* - If explicitType is provided, that executor is used (throws if unavailable).
|
|
22
23
|
* - If EXECUTOR_TYPE is set, that executor is used (throws if unavailable).
|
|
23
24
|
* - Otherwise, executors are tried in preference order (isolated-vm →
|
|
24
25
|
* container → vm2) and the first available one is selected.
|
|
25
26
|
*
|
|
26
27
|
* Returns both the executor and metadata about the selection.
|
|
27
28
|
*/
|
|
28
|
-
export declare function createExecutor(timeout?: number): Promise<{
|
|
29
|
+
export declare function createExecutor(timeout?: number, explicitType?: ExecutorType): Promise<{
|
|
29
30
|
executor: Executor;
|
|
30
31
|
info: ExecutorInfo;
|
|
31
32
|
}>;
|
package/dist/mcp/executor.js
CHANGED
|
@@ -3,36 +3,57 @@
|
|
|
3
3
|
* Selects the best available executor (isolated-vm → container → vm2)
|
|
4
4
|
*/
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
|
-
import { logInfo } from "../utils/logger.js";
|
|
6
|
+
import { logInfo, logDebug } from "../utils/logger.js";
|
|
7
7
|
// ── Availability checks (cached) ────────────────────────────────────
|
|
8
8
|
let _isolatedVmAvailable = null;
|
|
9
9
|
async function isIsolatedVmAvailable() {
|
|
10
10
|
if (_isolatedVmAvailable !== null)
|
|
11
11
|
return _isolatedVmAvailable;
|
|
12
|
+
logDebug('Checking isolated-vm availability...', { component: 'Executor' });
|
|
12
13
|
try {
|
|
13
|
-
//
|
|
14
|
-
|
|
14
|
+
// We run the check in a separate process because isolated-vm can cause
|
|
15
|
+
// segmentation faults if native dependencies or environment are incompatible.
|
|
16
|
+
// Running it here ensures a crash doesn't take down the main bridge process.
|
|
17
|
+
const checkScript = `
|
|
18
|
+
import ivm from 'isolated-vm';
|
|
19
|
+
const isolate = new ivm.Isolate({ memoryLimit: 8 });
|
|
20
|
+
isolate.dispose();
|
|
21
|
+
process.exit(0);
|
|
22
|
+
`;
|
|
23
|
+
const { execSync } = await import('node:child_process');
|
|
24
|
+
execSync(`node -e "${checkScript.replace(/"/g, '\\"').replace(/\n/g, '')}"`, { stdio: 'ignore', timeout: 5000 });
|
|
25
|
+
logDebug('isolated-vm is available and functional (verified via subprocess)', { component: 'Executor' });
|
|
15
26
|
_isolatedVmAvailable = true;
|
|
16
27
|
}
|
|
17
|
-
catch {
|
|
28
|
+
catch (err) {
|
|
29
|
+
logDebug(`isolated-vm check failed or crashed: ${err instanceof Error ? err.message : String(err)}`, { component: 'Executor' });
|
|
18
30
|
_isolatedVmAvailable = false;
|
|
19
31
|
}
|
|
20
32
|
return _isolatedVmAvailable;
|
|
21
33
|
}
|
|
22
34
|
let _containerRuntimeAvailable = null;
|
|
23
|
-
function isContainerRuntimeAvailable() {
|
|
35
|
+
async function isContainerRuntimeAvailable() {
|
|
24
36
|
if (_containerRuntimeAvailable !== null)
|
|
25
37
|
return _containerRuntimeAvailable;
|
|
38
|
+
logDebug('Checking container runtime availability...', { component: 'Executor' });
|
|
39
|
+
// Check for docker or podman
|
|
26
40
|
for (const cmd of ['docker', 'podman']) {
|
|
27
41
|
try {
|
|
28
|
-
|
|
42
|
+
logDebug(`Testing container runtime: ${cmd}`, { component: 'Executor' });
|
|
43
|
+
// Use 'ps' instead of '--version' because 'ps' requires the
|
|
44
|
+
// runtime daemon/service to be actually running and responsive.
|
|
45
|
+
// execFileSync is okay here as it's a one-time check during startup/first-use.
|
|
46
|
+
execFileSync(cmd, ['ps'], { stdio: 'ignore', timeout: 3000 });
|
|
47
|
+
logDebug(`Container runtime "${cmd}" is available and responsive`, { component: 'Executor' });
|
|
29
48
|
_containerRuntimeAvailable = true;
|
|
30
49
|
return true;
|
|
31
50
|
}
|
|
32
|
-
catch {
|
|
33
|
-
|
|
51
|
+
catch (err) {
|
|
52
|
+
logDebug(`Container runtime "${cmd}" check failed: ${err instanceof Error ? err.message : String(err)}`, { component: 'Executor' });
|
|
53
|
+
// not available or not running
|
|
34
54
|
}
|
|
35
55
|
}
|
|
56
|
+
logDebug('No responsive container runtime found', { component: 'Executor' });
|
|
36
57
|
_containerRuntimeAvailable = false;
|
|
37
58
|
return false;
|
|
38
59
|
}
|
|
@@ -50,7 +71,7 @@ const executorRegistry = [
|
|
|
50
71
|
{
|
|
51
72
|
type: 'container',
|
|
52
73
|
preference: 1,
|
|
53
|
-
isAvailable:
|
|
74
|
+
isAvailable: isContainerRuntimeAvailable,
|
|
54
75
|
async create(timeout) {
|
|
55
76
|
const { createContainerExecutor } = await import('../executor/container-executor.js');
|
|
56
77
|
return createContainerExecutor({ timeout });
|
|
@@ -79,26 +100,27 @@ const executorRegistry = [
|
|
|
79
100
|
* Factory function to create an Executor instance.
|
|
80
101
|
*
|
|
81
102
|
* Selection logic:
|
|
103
|
+
* - If explicitType is provided, that executor is used (throws if unavailable).
|
|
82
104
|
* - If EXECUTOR_TYPE is set, that executor is used (throws if unavailable).
|
|
83
105
|
* - Otherwise, executors are tried in preference order (isolated-vm →
|
|
84
106
|
* container → vm2) and the first available one is selected.
|
|
85
107
|
*
|
|
86
108
|
* Returns both the executor and metadata about the selection.
|
|
87
109
|
*/
|
|
88
|
-
export async function createExecutor(timeout = 30000) {
|
|
89
|
-
const requested = process.env.EXECUTOR_TYPE?.toLowerCase();
|
|
110
|
+
export async function createExecutor(timeout = 30000, explicitType) {
|
|
111
|
+
const requested = (explicitType || process.env.EXECUTOR_TYPE?.toLowerCase());
|
|
90
112
|
// Explicit selection — must succeed or throw
|
|
91
113
|
if (requested) {
|
|
92
114
|
const entry = executorRegistry.find(e => e.type === requested);
|
|
93
115
|
if (!entry) {
|
|
94
116
|
const known = executorRegistry.map(e => e.type).join(', ');
|
|
95
|
-
throw new Error(`Unknown
|
|
117
|
+
throw new Error(`Unknown executor type "${requested}". Valid types: ${known}`);
|
|
96
118
|
}
|
|
97
119
|
const available = await entry.isAvailable();
|
|
98
120
|
if (!available) {
|
|
99
|
-
throw new Error(`
|
|
121
|
+
throw new Error(`Executor type ${requested} requested but it is not available in this environment.`);
|
|
100
122
|
}
|
|
101
|
-
logInfo(`Using ${entry.type} executor (EXECUTOR_TYPE=${requested})`, { component: 'Executor' });
|
|
123
|
+
logInfo(`Using ${entry.type} executor (${explicitType ? 'explicit option' : `EXECUTOR_TYPE=${requested}`})`, { component: 'Executor' });
|
|
102
124
|
return {
|
|
103
125
|
executor: await entry.create(timeout),
|
|
104
126
|
info: { type: entry.type, reason: 'explicit', timeout },
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
* 6. Exposes the "codemode" tool via MCP protocol downstream
|
|
16
16
|
*/
|
|
17
17
|
import { z } from "zod";
|
|
18
|
+
import { type ExecutorType } from "./executor.js";
|
|
18
19
|
import { type MCPServerConfig } from "./mcp-client.js";
|
|
19
|
-
export type { MCPServerConfig };
|
|
20
|
+
export type { MCPServerConfig, ExecutorType };
|
|
20
21
|
/**
|
|
21
22
|
* Convert JSON Schema to Zod schema
|
|
22
23
|
* MCP tools use JSON Schema, but createCodeTool expects Zod schemas
|
|
23
24
|
*/
|
|
24
25
|
export declare function jsonSchemaToZod(schema: any): z.ZodType<any>;
|
|
25
|
-
export declare function startCodeModeBridgeServer(serverConfigs: MCPServerConfig[]): Promise<void>;
|
|
26
|
+
export declare function startCodeModeBridgeServer(serverConfigs: MCPServerConfig[], executorType?: ExecutorType): Promise<void>;
|
package/dist/mcp/server.js
CHANGED
|
@@ -219,7 +219,7 @@ function convertMCPToolToDescriptor(toolDef, client, toolName, serverName) {
|
|
|
219
219
|
},
|
|
220
220
|
};
|
|
221
221
|
}
|
|
222
|
-
export async function startCodeModeBridgeServer(serverConfigs) {
|
|
222
|
+
export async function startCodeModeBridgeServer(serverConfigs, executorType) {
|
|
223
223
|
// Enable buffering of stderr output from stdio tools during startup
|
|
224
224
|
enableStderrBuffering();
|
|
225
225
|
const mcp = new McpServer({
|
|
@@ -276,7 +276,7 @@ export async function startCodeModeBridgeServer(serverConfigs) {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
// Create the executor using the codemode SDK pattern
|
|
279
|
-
const { executor, info: executorInfo } = await createExecutor(30000); // 30 second timeout
|
|
279
|
+
const { executor, info: executorInfo } = await createExecutor(30000, executorType); // 30 second timeout
|
|
280
280
|
// Create the codemode tool using the codemode SDK
|
|
281
281
|
// Pass ToolDescriptor format (with Zod schemas and execute functions)
|
|
282
282
|
logInfo(`Creating codemode tool with ${totalToolCount} tools from ${serverConfigs.length} server(s)`, { component: 'Bridge' });
|
package/package.json
CHANGED