@romiluz/clawmongo 0.1.0-rc.2 → 0.1.0-rc.3
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/main.js +1 -1
- package/dist/modules/tool-runtime/executors/bash.js +234 -0
- package/dist/modules/tool-runtime/executors/index.js +4 -0
- package/dist/modules/tool-runtime/executors/server-tools.js +70 -0
- package/dist/modules/tool-runtime/executors/think.js +79 -0
- package/dist/modules/tool-runtime/mcp/connection.js +178 -0
- package/dist/modules/tool-runtime/mcp/index.js +11 -0
- package/dist/modules/tool-runtime/mcp/tool.js +72 -0
- package/dist/modules/tool-runtime/mcp/types.js +9 -0
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -19,7 +19,7 @@ import { cronCommand } from "./cli/commands/cron.js";
|
|
|
19
19
|
import { backupCommand } from "./cli/commands/backup.js";
|
|
20
20
|
import { securityCommand } from "./cli/commands/security.js";
|
|
21
21
|
import { benchmarkCommand } from "./cli/commands/benchmark.js";
|
|
22
|
-
const VERSION = "0.1.0-rc.
|
|
22
|
+
const VERSION = "0.1.0-rc.3";
|
|
23
23
|
/**
|
|
24
24
|
* Build the command registry.
|
|
25
25
|
*/
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash Session Tool
|
|
3
|
+
*
|
|
4
|
+
* Persistent bash shell sessions with restart capability.
|
|
5
|
+
* Ported from OpenClaw's computer-use-demo/tools/bash.py.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Persistent sessions that maintain state
|
|
9
|
+
* - Automatic session creation
|
|
10
|
+
* - Restart capability for stuck sessions
|
|
11
|
+
* - Timeout handling
|
|
12
|
+
* - Tenant-scoped session management
|
|
13
|
+
*
|
|
14
|
+
* @module tool-runtime/executors/bash
|
|
15
|
+
*/
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
17
|
+
import { randomUUID } from "node:crypto";
|
|
18
|
+
const DEFAULT_TIMEOUT_MS = 120_000; // 2 minutes
|
|
19
|
+
const OUTPUT_DELAY_MS = 200;
|
|
20
|
+
const SENTINEL = "<<exit>>";
|
|
21
|
+
/**
|
|
22
|
+
* Session registry - maps tenant to their active bash session
|
|
23
|
+
*/
|
|
24
|
+
const sessionRegistry = new Map();
|
|
25
|
+
/**
|
|
26
|
+
* Get or create a bash session for a tenant
|
|
27
|
+
*/
|
|
28
|
+
function getSession(tenantId) {
|
|
29
|
+
return sessionRegistry.get(tenantId);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a new bash session
|
|
33
|
+
*/
|
|
34
|
+
async function createSession(tenantId) {
|
|
35
|
+
// Kill existing session if any
|
|
36
|
+
const existing = sessionRegistry.get(tenantId);
|
|
37
|
+
if (existing?.process) {
|
|
38
|
+
existing.process.kill();
|
|
39
|
+
}
|
|
40
|
+
const sessionId = `bash-${randomUUID().slice(0, 8)}`;
|
|
41
|
+
const proc = spawn("/bin/bash", [], {
|
|
42
|
+
shell: false,
|
|
43
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
44
|
+
});
|
|
45
|
+
const session = {
|
|
46
|
+
id: sessionId,
|
|
47
|
+
process: proc,
|
|
48
|
+
tenantId,
|
|
49
|
+
started: true,
|
|
50
|
+
timedOut: false,
|
|
51
|
+
stdout: "",
|
|
52
|
+
stderr: "",
|
|
53
|
+
createdAt: new Date()
|
|
54
|
+
};
|
|
55
|
+
sessionRegistry.set(tenantId, session);
|
|
56
|
+
return session;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Run a command in a bash session
|
|
60
|
+
*/
|
|
61
|
+
async function runCommand(session, command, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
62
|
+
if (!session.started) {
|
|
63
|
+
return { stdout: "", stderr: "", error: "Session has not started" };
|
|
64
|
+
}
|
|
65
|
+
if (session.process.exitCode !== null) {
|
|
66
|
+
return {
|
|
67
|
+
stdout: "",
|
|
68
|
+
stderr: "",
|
|
69
|
+
error: `Bash has exited with return code ${session.process.exitCode}. Tool must be restarted.`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (session.timedOut) {
|
|
73
|
+
return {
|
|
74
|
+
stdout: "",
|
|
75
|
+
stderr: "",
|
|
76
|
+
error: `Session timed out and must be restarted`
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// Clear buffers
|
|
80
|
+
session.stdout = "";
|
|
81
|
+
session.stderr = "";
|
|
82
|
+
// Setup output collection
|
|
83
|
+
const stdoutHandler = (data) => {
|
|
84
|
+
session.stdout += data.toString();
|
|
85
|
+
};
|
|
86
|
+
const stderrHandler = (data) => {
|
|
87
|
+
session.stderr += data.toString();
|
|
88
|
+
};
|
|
89
|
+
session.process.stdout?.on("data", stdoutHandler);
|
|
90
|
+
session.process.stderr?.on("data", stderrHandler);
|
|
91
|
+
// Send command with sentinel
|
|
92
|
+
session.process.stdin?.write(`${command}; echo '${SENTINEL}'\n`);
|
|
93
|
+
// Wait for output with sentinel
|
|
94
|
+
try {
|
|
95
|
+
await new Promise((resolve, reject) => {
|
|
96
|
+
const timeout = setTimeout(() => {
|
|
97
|
+
session.timedOut = true;
|
|
98
|
+
reject(new Error(`Bash timed out after ${timeoutMs}ms and must be restarted`));
|
|
99
|
+
}, timeoutMs);
|
|
100
|
+
const checkInterval = setInterval(() => {
|
|
101
|
+
if (session.stdout.includes(SENTINEL)) {
|
|
102
|
+
clearTimeout(timeout);
|
|
103
|
+
clearInterval(checkInterval);
|
|
104
|
+
resolve();
|
|
105
|
+
}
|
|
106
|
+
}, OUTPUT_DELAY_MS);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
session.process.stdout?.removeListener("data", stdoutHandler);
|
|
111
|
+
session.process.stderr?.removeListener("data", stderrHandler);
|
|
112
|
+
return {
|
|
113
|
+
stdout: "",
|
|
114
|
+
stderr: "",
|
|
115
|
+
error: err instanceof Error ? err.message : String(err)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
session.process.stdout?.removeListener("data", stdoutHandler);
|
|
119
|
+
session.process.stderr?.removeListener("data", stderrHandler);
|
|
120
|
+
// Strip sentinel from output
|
|
121
|
+
let output = session.stdout;
|
|
122
|
+
const sentinelIdx = output.indexOf(SENTINEL);
|
|
123
|
+
if (sentinelIdx !== -1) {
|
|
124
|
+
output = output.slice(0, sentinelIdx);
|
|
125
|
+
}
|
|
126
|
+
if (output.endsWith("\n")) {
|
|
127
|
+
output = output.slice(0, -1);
|
|
128
|
+
}
|
|
129
|
+
let stderr = session.stderr;
|
|
130
|
+
if (stderr.endsWith("\n")) {
|
|
131
|
+
stderr = stderr.slice(0, -1);
|
|
132
|
+
}
|
|
133
|
+
return { stdout: output, stderr };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Validate bash tool parameters
|
|
137
|
+
*/
|
|
138
|
+
function validateBashParams(params) {
|
|
139
|
+
const command = params.command;
|
|
140
|
+
const restart = params.restart;
|
|
141
|
+
if (restart === true) {
|
|
142
|
+
return { restart: true };
|
|
143
|
+
}
|
|
144
|
+
if (typeof command !== "string" || command.trim().length === 0) {
|
|
145
|
+
return { error: "command is required (or set restart: true)" };
|
|
146
|
+
}
|
|
147
|
+
return { command: command.trim(), restart: false };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Bash tool executor
|
|
151
|
+
*/
|
|
152
|
+
export async function bashToolExecutor(params, context) {
|
|
153
|
+
const startTime = performance.now();
|
|
154
|
+
const tenantId = context.tenantId ?? "default";
|
|
155
|
+
// Handle restart
|
|
156
|
+
if (params.restart) {
|
|
157
|
+
await createSession(tenantId);
|
|
158
|
+
return {
|
|
159
|
+
ok: true,
|
|
160
|
+
output: { system: "Tool has been restarted.", stdout: "", stderr: "" },
|
|
161
|
+
durationMs: Math.round(performance.now() - startTime),
|
|
162
|
+
sideEffects: [`bash:restart:${context.toolCallId}`]
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// Get or create session
|
|
166
|
+
let session = getSession(tenantId);
|
|
167
|
+
if (!session) {
|
|
168
|
+
session = await createSession(tenantId);
|
|
169
|
+
}
|
|
170
|
+
// Run command
|
|
171
|
+
const result = await runCommand(session, params.command ?? "");
|
|
172
|
+
if (result.error) {
|
|
173
|
+
return {
|
|
174
|
+
ok: false,
|
|
175
|
+
output: null,
|
|
176
|
+
error: result.error,
|
|
177
|
+
durationMs: Math.round(performance.now() - startTime),
|
|
178
|
+
sideEffects: []
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
ok: true,
|
|
183
|
+
output: { stdout: result.stdout, stderr: result.stderr },
|
|
184
|
+
durationMs: Math.round(performance.now() - startTime),
|
|
185
|
+
sideEffects: [`bash:exec:${context.toolCallId}`]
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Create bash executor with validated params
|
|
190
|
+
*/
|
|
191
|
+
export function createBashExecutor(rawParams, context) {
|
|
192
|
+
const validation = validateBashParams(rawParams);
|
|
193
|
+
if ("error" in validation) {
|
|
194
|
+
return {
|
|
195
|
+
ok: false,
|
|
196
|
+
output: null,
|
|
197
|
+
error: validation.error,
|
|
198
|
+
durationMs: 0,
|
|
199
|
+
sideEffects: []
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return bashToolExecutor(validation, context);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Cleanup bash sessions for a tenant
|
|
206
|
+
*/
|
|
207
|
+
export function cleanupBashSessions(tenantId) {
|
|
208
|
+
const session = sessionRegistry.get(tenantId);
|
|
209
|
+
if (session?.process) {
|
|
210
|
+
session.process.kill();
|
|
211
|
+
}
|
|
212
|
+
sessionRegistry.delete(tenantId);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Tool definition for the bash tool
|
|
216
|
+
*/
|
|
217
|
+
export const bashToolDefinition = {
|
|
218
|
+
name: "bash",
|
|
219
|
+
description: "Run commands in a persistent bash shell. The session persists between calls. " +
|
|
220
|
+
"Set restart: true to restart the shell if it times out or exits.",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
command: {
|
|
225
|
+
type: "string",
|
|
226
|
+
description: "The bash command to run."
|
|
227
|
+
},
|
|
228
|
+
restart: {
|
|
229
|
+
type: "boolean",
|
|
230
|
+
description: "Set to true to restart the bash session."
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
@@ -8,3 +8,7 @@ export { createExecExecutor, execToolExecutor } from "./exec.js";
|
|
|
8
8
|
export { cleanupTenantProcesses, createProcessExecutor, getProcessCount, processToolExecutor } from "./process.js";
|
|
9
9
|
export { createEditExecutor, createReadExecutor, createWriteExecutor, editToolExecutor, readToolExecutor, writeToolExecutor } from "./filesystem.js";
|
|
10
10
|
export { createSessionsListExecutor, createSessionStatusExecutor, sessionsListExecutor, sessionStatusExecutor } from "./session.js";
|
|
11
|
+
// New tools for OpenClaw parity
|
|
12
|
+
export { createThinkExecutor, thinkToolExecutor, thinkToolDefinition } from "./think.js";
|
|
13
|
+
export { createBashExecutor, bashToolExecutor, bashToolDefinition, cleanupBashSessions } from "./bash.js";
|
|
14
|
+
export { createCodeExecutionServerTool, createWebSearchServerTool, getDefaultServerTools, getServerToolBetaHeader, isServerTool, SERVER_TOOL_TYPES } from "./server-tools.js";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Tools - Anthropic Server-Side Tool Definitions
|
|
3
|
+
*
|
|
4
|
+
* These are special tools executed by Anthropic's servers, not locally.
|
|
5
|
+
* They use a different registration format and don't need local executors.
|
|
6
|
+
*
|
|
7
|
+
* Ported from OpenClaw's anthropic-quickstarts:
|
|
8
|
+
* - code_execution.py → CodeExecutionServerTool
|
|
9
|
+
* - web_search.py → WebSearchServerTool
|
|
10
|
+
*
|
|
11
|
+
* @module tool-runtime/executors/server-tools
|
|
12
|
+
*/
|
|
13
|
+
export function createCodeExecutionServerTool(options = {}) {
|
|
14
|
+
const name = options.name ?? "code_execution";
|
|
15
|
+
return {
|
|
16
|
+
type: "code_execution_20250522",
|
|
17
|
+
name
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function createWebSearchServerTool(options = {}) {
|
|
21
|
+
const name = options.name ?? "web_search";
|
|
22
|
+
const tool = {
|
|
23
|
+
type: "web_search_20250305",
|
|
24
|
+
name
|
|
25
|
+
};
|
|
26
|
+
if (options.maxUses !== undefined) {
|
|
27
|
+
tool.max_uses = options.maxUses;
|
|
28
|
+
}
|
|
29
|
+
if (options.allowedDomains !== undefined) {
|
|
30
|
+
tool.allowed_domains = options.allowedDomains;
|
|
31
|
+
}
|
|
32
|
+
if (options.blockedDomains !== undefined) {
|
|
33
|
+
tool.blocked_domains = options.blockedDomains;
|
|
34
|
+
}
|
|
35
|
+
if (options.userLocation !== undefined) {
|
|
36
|
+
tool.user_location = options.userLocation;
|
|
37
|
+
}
|
|
38
|
+
return tool;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Server tool type constants
|
|
42
|
+
*/
|
|
43
|
+
export const SERVER_TOOL_TYPES = {
|
|
44
|
+
CODE_EXECUTION: "code_execution_20250522",
|
|
45
|
+
WEB_SEARCH: "web_search_20250305"
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Check if a tool definition is a server tool
|
|
49
|
+
*/
|
|
50
|
+
export function isServerTool(toolDef) {
|
|
51
|
+
const type = toolDef.type;
|
|
52
|
+
return (type === SERVER_TOOL_TYPES.CODE_EXECUTION ||
|
|
53
|
+
type === SERVER_TOOL_TYPES.WEB_SEARCH);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get required beta header for server tools
|
|
57
|
+
*/
|
|
58
|
+
export function getServerToolBetaHeader() {
|
|
59
|
+
// The code execution tool requires this beta header
|
|
60
|
+
return "code-execution-2025-05-22";
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Default server tools preset - both code execution and web search
|
|
64
|
+
*/
|
|
65
|
+
export function getDefaultServerTools() {
|
|
66
|
+
return [
|
|
67
|
+
createCodeExecutionServerTool(),
|
|
68
|
+
createWebSearchServerTool()
|
|
69
|
+
];
|
|
70
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Think Tool Executor
|
|
3
|
+
*
|
|
4
|
+
* Internal reasoning tool for the agent to think through problems
|
|
5
|
+
* without executing external actions. Ported from OpenClaw's think.py.
|
|
6
|
+
*
|
|
7
|
+
* This tool allows the model to:
|
|
8
|
+
* - Reason about complex problems step by step
|
|
9
|
+
* - Store intermediate thoughts in conversation history
|
|
10
|
+
* - Plan before taking actions
|
|
11
|
+
*
|
|
12
|
+
* @module tool-runtime/executors/think
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Validate think tool parameters
|
|
16
|
+
*/
|
|
17
|
+
function validateThinkParams(params) {
|
|
18
|
+
const thought = params.thought;
|
|
19
|
+
if (typeof thought !== "string" || thought.trim().length === 0) {
|
|
20
|
+
return { error: "thought is required and must be a non-empty string" };
|
|
21
|
+
}
|
|
22
|
+
return { thought: thought.trim() };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Think tool executor
|
|
26
|
+
*
|
|
27
|
+
* Simply acknowledges the thought, allowing the model to use it
|
|
28
|
+
* for chain-of-thought reasoning without external side effects.
|
|
29
|
+
*/
|
|
30
|
+
export const thinkToolExecutor = async (params, _context) => {
|
|
31
|
+
const startTime = performance.now();
|
|
32
|
+
// The think tool doesn't do anything external -
|
|
33
|
+
// it just acknowledges the thought so the model can reason
|
|
34
|
+
const result = {
|
|
35
|
+
message: "Thinking complete!",
|
|
36
|
+
thought: params.thought
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
ok: true,
|
|
40
|
+
output: result,
|
|
41
|
+
durationMs: Math.round(performance.now() - startTime),
|
|
42
|
+
sideEffects: [] // No side effects - purely internal reasoning
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Create think executor with validated params
|
|
47
|
+
*/
|
|
48
|
+
export function createThinkExecutor(rawParams, context) {
|
|
49
|
+
const validation = validateThinkParams(rawParams);
|
|
50
|
+
if ("error" in validation) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
output: null,
|
|
54
|
+
error: validation.error,
|
|
55
|
+
durationMs: 0,
|
|
56
|
+
sideEffects: []
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return thinkToolExecutor(validation, context);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Tool definition for the think tool (for registration)
|
|
63
|
+
*/
|
|
64
|
+
export const thinkToolDefinition = {
|
|
65
|
+
name: "think",
|
|
66
|
+
description: "Use this tool to think about something. It will not obtain new information " +
|
|
67
|
+
"or change the database, but just append the thought to the log. " +
|
|
68
|
+
"Use it when complex reasoning or some cache memory is needed.",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: "object",
|
|
71
|
+
properties: {
|
|
72
|
+
thought: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "A thought to think about."
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
required: ["thought"]
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Connection Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages connections to MCP (Model Context Protocol) tool servers.
|
|
5
|
+
* Supports both STDIO and SSE connection types.
|
|
6
|
+
*
|
|
7
|
+
* @module tool-runtime/mcp/connection
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
/**
|
|
12
|
+
* MCP Connection - manages a single MCP server connection
|
|
13
|
+
*/
|
|
14
|
+
export class MCPConnection {
|
|
15
|
+
id;
|
|
16
|
+
config;
|
|
17
|
+
process = null;
|
|
18
|
+
connected = false;
|
|
19
|
+
tools = [];
|
|
20
|
+
error;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.id = `mcp-${randomUUID().slice(0, 8)}`;
|
|
23
|
+
this.config = config;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Connect to the MCP server
|
|
27
|
+
*/
|
|
28
|
+
async connect() {
|
|
29
|
+
if (this.connected)
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
if (this.config.type === "stdio") {
|
|
33
|
+
await this.connectStdio();
|
|
34
|
+
}
|
|
35
|
+
else if (this.config.type === "sse") {
|
|
36
|
+
await this.connectSSE();
|
|
37
|
+
}
|
|
38
|
+
this.connected = true;
|
|
39
|
+
this.error = undefined;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Connect via STDIO
|
|
48
|
+
*/
|
|
49
|
+
async connectStdio() {
|
|
50
|
+
const config = this.config;
|
|
51
|
+
this.process = spawn(config.command, config.args ?? [], {
|
|
52
|
+
env: { ...process.env, ...config.env },
|
|
53
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
54
|
+
});
|
|
55
|
+
// Wait for connection to be established
|
|
56
|
+
await new Promise((resolve, reject) => {
|
|
57
|
+
const timeout = setTimeout(() => {
|
|
58
|
+
reject(new Error("MCP connection timeout"));
|
|
59
|
+
}, 10000);
|
|
60
|
+
this.process?.stdout?.once("data", () => {
|
|
61
|
+
clearTimeout(timeout);
|
|
62
|
+
resolve();
|
|
63
|
+
});
|
|
64
|
+
this.process?.on("error", (err) => {
|
|
65
|
+
clearTimeout(timeout);
|
|
66
|
+
reject(err);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Connect via SSE (placeholder - full implementation needs SSE client)
|
|
72
|
+
*/
|
|
73
|
+
async connectSSE() {
|
|
74
|
+
// SSE implementation would use EventSource or similar
|
|
75
|
+
// For now, throw not implemented
|
|
76
|
+
throw new Error("SSE MCP connections not yet implemented - use STDIO");
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Disconnect from the MCP server
|
|
80
|
+
*/
|
|
81
|
+
disconnect() {
|
|
82
|
+
if (this.process) {
|
|
83
|
+
this.process.kill();
|
|
84
|
+
this.process = null;
|
|
85
|
+
}
|
|
86
|
+
this.connected = false;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* List available tools from the server
|
|
90
|
+
*/
|
|
91
|
+
async listTools() {
|
|
92
|
+
if (!this.connected) {
|
|
93
|
+
throw new Error("Not connected to MCP server");
|
|
94
|
+
}
|
|
95
|
+
// In full implementation, this would send list_tools request
|
|
96
|
+
// For now, return cached tools
|
|
97
|
+
return this.tools;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Call a tool on the MCP server
|
|
101
|
+
*/
|
|
102
|
+
async callTool(name, args) {
|
|
103
|
+
if (!this.connected) {
|
|
104
|
+
throw new Error("Not connected to MCP server");
|
|
105
|
+
}
|
|
106
|
+
// In full implementation, this would:
|
|
107
|
+
// 1. Send JSON-RPC request to server
|
|
108
|
+
// 2. Wait for response
|
|
109
|
+
// 3. Parse and return result
|
|
110
|
+
// Placeholder response
|
|
111
|
+
return {
|
|
112
|
+
content: [{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: `MCP tool ${name} called with args: ${JSON.stringify(args)}`
|
|
115
|
+
}]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get current connection state
|
|
120
|
+
*/
|
|
121
|
+
getState() {
|
|
122
|
+
return {
|
|
123
|
+
id: this.id,
|
|
124
|
+
config: this.config,
|
|
125
|
+
connected: this.connected,
|
|
126
|
+
tools: this.tools,
|
|
127
|
+
error: this.error
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* MCP Connection Manager - manages multiple MCP connections
|
|
133
|
+
*/
|
|
134
|
+
export class MCPConnectionManager {
|
|
135
|
+
connections = new Map();
|
|
136
|
+
/**
|
|
137
|
+
* Create and connect to an MCP server
|
|
138
|
+
*/
|
|
139
|
+
async connect(config) {
|
|
140
|
+
const connection = new MCPConnection(config);
|
|
141
|
+
await connection.connect();
|
|
142
|
+
this.connections.set(connection.id, connection);
|
|
143
|
+
return connection;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Disconnect from an MCP server
|
|
147
|
+
*/
|
|
148
|
+
disconnect(connectionId) {
|
|
149
|
+
const connection = this.connections.get(connectionId);
|
|
150
|
+
if (connection) {
|
|
151
|
+
connection.disconnect();
|
|
152
|
+
this.connections.delete(connectionId);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Disconnect all MCP servers
|
|
157
|
+
*/
|
|
158
|
+
disconnectAll() {
|
|
159
|
+
for (const connection of this.connections.values()) {
|
|
160
|
+
connection.disconnect();
|
|
161
|
+
}
|
|
162
|
+
this.connections.clear();
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get a connection by ID
|
|
166
|
+
*/
|
|
167
|
+
get(connectionId) {
|
|
168
|
+
return this.connections.get(connectionId);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* List all connections
|
|
172
|
+
*/
|
|
173
|
+
list() {
|
|
174
|
+
return Array.from(this.connections.values()).map(c => c.getState());
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Singleton manager instance
|
|
178
|
+
export const mcpManager = new MCPConnectionManager();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP (Model Context Protocol) Module
|
|
3
|
+
*
|
|
4
|
+
* Enables ClawMongo to connect to external MCP tool servers.
|
|
5
|
+
* Provides full parity with OpenClaw's MCP tool integration.
|
|
6
|
+
*
|
|
7
|
+
* @module tool-runtime/mcp
|
|
8
|
+
*/
|
|
9
|
+
export * from "./types.js";
|
|
10
|
+
export { MCPConnection, MCPConnectionManager, mcpManager } from "./connection.js";
|
|
11
|
+
export { createMCPTool, createMCPToolExecutor, mcpToolToDefinition } from "./tool.js";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool - Wrapper for MCP server tools
|
|
3
|
+
*
|
|
4
|
+
* Creates executable tool instances from MCP server tool definitions.
|
|
5
|
+
* Ported from OpenClaw's mcp_tool.py.
|
|
6
|
+
*
|
|
7
|
+
* @module tool-runtime/mcp/tool
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Create an MCP tool from a server tool definition
|
|
11
|
+
*/
|
|
12
|
+
export function createMCPTool(definition, connection) {
|
|
13
|
+
return {
|
|
14
|
+
name: definition.name,
|
|
15
|
+
description: definition.description ?? `MCP tool: ${definition.name}`,
|
|
16
|
+
inputSchema: definition.inputSchema,
|
|
17
|
+
connection,
|
|
18
|
+
async execute(args) {
|
|
19
|
+
try {
|
|
20
|
+
const result = await connection.callTool(definition.name, args);
|
|
21
|
+
// Extract text content from result
|
|
22
|
+
if (result.content && result.content.length > 0) {
|
|
23
|
+
for (const item of result.content) {
|
|
24
|
+
if (item.type === "text" && item.text) {
|
|
25
|
+
return item.text;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return "No text content in MCP tool response";
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
return `Error executing MCP tool ${definition.name}: ${err instanceof Error ? err.message : String(err)}`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create an executor for an MCP tool
|
|
39
|
+
*/
|
|
40
|
+
export function createMCPToolExecutor(tool) {
|
|
41
|
+
return async (params, context) => {
|
|
42
|
+
const startTime = performance.now();
|
|
43
|
+
try {
|
|
44
|
+
const result = await tool.execute(params);
|
|
45
|
+
return {
|
|
46
|
+
ok: true,
|
|
47
|
+
output: result,
|
|
48
|
+
durationMs: Math.round(performance.now() - startTime),
|
|
49
|
+
sideEffects: [`mcp:${tool.name}:${context.toolCallId}`]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
output: null,
|
|
56
|
+
error: err instanceof Error ? err.message : String(err),
|
|
57
|
+
durationMs: Math.round(performance.now() - startTime),
|
|
58
|
+
sideEffects: []
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Convert MCP tool to standard tool definition format
|
|
65
|
+
*/
|
|
66
|
+
export function mcpToolToDefinition(tool) {
|
|
67
|
+
return {
|
|
68
|
+
name: tool.name,
|
|
69
|
+
description: tool.description,
|
|
70
|
+
input_schema: tool.inputSchema
|
|
71
|
+
};
|
|
72
|
+
}
|