@toolforest/toolforest-plugin 0.3.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 +68 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/src/client.d.ts +34 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +120 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/config.d.ts +10 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +19 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/prompt.d.ts +7 -0
- package/dist/src/prompt.d.ts.map +1 -0
- package/dist/src/prompt.js +37 -0
- package/dist/src/prompt.js.map +1 -0
- package/dist/src/tool-bridge.d.ts +33 -0
- package/dist/src/tool-bridge.d.ts.map +1 -0
- package/dist/src/tool-bridge.js +59 -0
- package/dist/src/tool-bridge.js.map +1 -0
- package/dist/src/toolkit-cache.d.ts +19 -0
- package/dist/src/toolkit-cache.d.ts.map +1 -0
- package/dist/src/toolkit-cache.js +48 -0
- package/dist/src/toolkit-cache.js.map +1 -0
- package/dist/src/types.d.ts +46 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +5 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/validate.d.ts +14 -0
- package/dist/src/validate.d.ts.map +1 -0
- package/dist/src/validate.js +85 -0
- package/dist/src/validate.js.map +1 -0
- package/openclaw.plugin.json +33 -0
- package/package.json +55 -0
- package/skills/toolforest-mcp/SKILL.md +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# @toolforest/openclaw-plugin
|
|
2
|
+
|
|
3
|
+
OpenClaw plugin that connects your [Toolforest](https://www.toolforest.io) toolkits as native agent tools. Supports 250+ tools across GitHub, Google Sheets, Slack, and more.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
openclaw plugins install @toolforest/openclaw-plugin
|
|
9
|
+
openclaw gateway restart
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
Set your Toolforest API key (get one at [app.toolforest.io](https://app.toolforest.io)):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
openclaw config set plugins.entries.toolforest.config.apiKey tfo_your_key_here
|
|
18
|
+
openclaw gateway restart
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or set the `TOOLFOREST_API_KEY` environment variable.
|
|
22
|
+
|
|
23
|
+
### Options
|
|
24
|
+
|
|
25
|
+
| Key | Description | Default |
|
|
26
|
+
|-----|-------------|---------|
|
|
27
|
+
| `apiKey` | Toolforest API key (`tfo_...`) | `TOOLFOREST_API_KEY` env var |
|
|
28
|
+
| `remoteUrl` | Override the MCP endpoint URL | `https://mcp.toolforest.io/mcp` |
|
|
29
|
+
|
|
30
|
+
## How it works
|
|
31
|
+
|
|
32
|
+
1. Connects to the Toolforest MCP server using your API key
|
|
33
|
+
2. Discovers all connected toolkits and their tools
|
|
34
|
+
3. Registers each tool as a native OpenClaw agent tool (prefixed with `toolforest_`)
|
|
35
|
+
4. Injects prompt guidance so the agent knows which toolkits are available
|
|
36
|
+
5. Refreshes the toolkit list in the background every 5 minutes
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- **Native tool registration** — Toolforest tools appear as first-class OpenClaw tools
|
|
41
|
+
- **Dynamic prompt guidance** — agent sees which toolkits are connected, updated every 5 minutes
|
|
42
|
+
- **Error state guidance** — if connection fails, the agent guides the user through setup
|
|
43
|
+
- **Fallback skill** — `/toolforest-mcp` skill provides curl-based MCP access when native tools are unavailable
|
|
44
|
+
- **Client-side validation** — validates tool arguments locally before sending to the server
|
|
45
|
+
|
|
46
|
+
## Fallback skill
|
|
47
|
+
|
|
48
|
+
If the native tools aren't working, use the built-in fallback skill:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
/toolforest-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This gives the agent curl-based instructions to call the Toolforest MCP server directly.
|
|
55
|
+
|
|
56
|
+
## Requirements
|
|
57
|
+
|
|
58
|
+
- OpenClaw >= 2026.3.0
|
|
59
|
+
- Node.js >= 20
|
|
60
|
+
- A Toolforest account with at least one connected toolkit
|
|
61
|
+
|
|
62
|
+
## Existing sessions
|
|
63
|
+
|
|
64
|
+
After installing or updating the plugin, restart any existing OpenClaw sessions. The plugin will only be available in sessions started after the gateway restart.
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @toolforest/openclaw-plugin
|
|
3
|
+
*
|
|
4
|
+
* OpenClaw plugin that connects to the Toolforest MCP server and registers
|
|
5
|
+
* all connected tools as native agent tools. Follows the same pattern as
|
|
6
|
+
* openclaw/src/agents/pi-bundle-mcp-tools.ts for MCP-to-AgentTool bridging.
|
|
7
|
+
*/
|
|
8
|
+
declare const pluginDefinition: {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
register(api: PluginApi): Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
interface PluginApi {
|
|
15
|
+
pluginConfig?: unknown;
|
|
16
|
+
logger: {
|
|
17
|
+
info(msg: string): void;
|
|
18
|
+
warn(msg: string): void;
|
|
19
|
+
error(msg: string): void;
|
|
20
|
+
debug(msg: string): void;
|
|
21
|
+
};
|
|
22
|
+
registerTool(tool: never, opts?: {
|
|
23
|
+
name?: string;
|
|
24
|
+
}): void;
|
|
25
|
+
on?(event: string, handler: (...args: unknown[]) => unknown): void;
|
|
26
|
+
}
|
|
27
|
+
export default pluginDefinition;
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,QAAA,MAAM,gBAAgB;;;;kBAMA,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CA6E9C,CAAC;AAGF,UAAU,SAAS;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE;QACN,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,CAAC;IACF,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1D,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;CACpE;AAED,eAAe,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @toolforest/openclaw-plugin
|
|
3
|
+
*
|
|
4
|
+
* OpenClaw plugin that connects to the Toolforest MCP server and registers
|
|
5
|
+
* all connected tools as native agent tools. Follows the same pattern as
|
|
6
|
+
* openclaw/src/agents/pi-bundle-mcp-tools.ts for MCP-to-AgentTool bridging.
|
|
7
|
+
*/
|
|
8
|
+
import { ToolforestClient } from "./src/client.js";
|
|
9
|
+
import { resolveConfig } from "./src/config.js";
|
|
10
|
+
import { buildBlock } from "./src/prompt.js";
|
|
11
|
+
import { bridgeAllTools } from "./src/tool-bridge.js";
|
|
12
|
+
import { ToolkitCache } from "./src/toolkit-cache.js";
|
|
13
|
+
const pluginDefinition = {
|
|
14
|
+
id: "toolforest",
|
|
15
|
+
name: "Toolforest",
|
|
16
|
+
description: "Connect all your Toolforest toolkits as native OpenClaw agent tools.",
|
|
17
|
+
async register(api) {
|
|
18
|
+
const cfg = resolveConfig(api.pluginConfig);
|
|
19
|
+
// Mutable state read by the hook on every agent turn
|
|
20
|
+
let promptState = {
|
|
21
|
+
status: "error",
|
|
22
|
+
message: "Not configured",
|
|
23
|
+
};
|
|
24
|
+
let cache = null;
|
|
25
|
+
let toolCount = 0;
|
|
26
|
+
// Register hook FIRST — before any async work.
|
|
27
|
+
// This ensures the agent always gets prompt guidance, even on error.
|
|
28
|
+
if (typeof api.on === "function") {
|
|
29
|
+
api.on("before_prompt_build", () => {
|
|
30
|
+
if (cache) {
|
|
31
|
+
return {
|
|
32
|
+
prependContext: buildBlock({
|
|
33
|
+
status: "ready",
|
|
34
|
+
toolkits: cache.getToolkits(),
|
|
35
|
+
toolCount,
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return { prependContext: buildBlock(promptState) };
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (!cfg.apiKey) {
|
|
43
|
+
promptState = {
|
|
44
|
+
status: "error",
|
|
45
|
+
message: "No API key configured. Set plugins.entries.toolforest.config.apiKey " +
|
|
46
|
+
"or TOOLFOREST_API_KEY env var.",
|
|
47
|
+
};
|
|
48
|
+
api.logger.warn("toolforest: " + promptState.message);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const client = new ToolforestClient();
|
|
52
|
+
try {
|
|
53
|
+
await client.connect(cfg.remoteUrl, cfg.apiKey);
|
|
54
|
+
api.logger.info(`toolforest: Connected to ${cfg.remoteUrl}`);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
promptState = {
|
|
58
|
+
status: "error",
|
|
59
|
+
message: `Failed to connect to ${cfg.remoteUrl}: ${err instanceof Error ? err.message : String(err)}`,
|
|
60
|
+
};
|
|
61
|
+
api.logger.warn("toolforest: " + promptState.message);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const { toolkits, tools } = await client.discoverTools();
|
|
66
|
+
const bridged = bridgeAllTools(tools, client);
|
|
67
|
+
toolCount = bridged.length;
|
|
68
|
+
for (const tool of bridged) {
|
|
69
|
+
api.registerTool(tool, { name: tool.name });
|
|
70
|
+
}
|
|
71
|
+
cache = new ToolkitCache(client, api.logger);
|
|
72
|
+
cache.seed(toolkits);
|
|
73
|
+
api.logger.info(`toolforest: Registered ${toolCount} tools from ${toolkits.length} toolkits`);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
promptState = {
|
|
77
|
+
status: "error",
|
|
78
|
+
message: `Failed to discover tools: ${err instanceof Error ? err.message : String(err)}`,
|
|
79
|
+
};
|
|
80
|
+
api.logger.warn("toolforest: " + promptState.message);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
export default pluginDefinition;
|
|
85
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolforest MCP client — connects to the remote Toolforest MCP server,
|
|
3
|
+
* discovers all connected tools, and forwards execution requests.
|
|
4
|
+
*/
|
|
5
|
+
import type { ToolforestToolDescriptor, ToolforestToolkit, ToolExecutionResult } from "./types.js";
|
|
6
|
+
export declare class ToolforestClient {
|
|
7
|
+
private client;
|
|
8
|
+
private transport;
|
|
9
|
+
/** Cached tool schemas for client-side validation. */
|
|
10
|
+
private schemaCache;
|
|
11
|
+
connect(url: string, apiKey: string): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Discover all connected tools by calling the router meta-tools.
|
|
14
|
+
* 1. list_tools() → get connected toolkit names
|
|
15
|
+
* 2. list_tools(toolkit=X) for each → get tool descriptors with schemas
|
|
16
|
+
*/
|
|
17
|
+
discoverTools(): Promise<{
|
|
18
|
+
toolkits: ToolforestToolkit[];
|
|
19
|
+
tools: ToolforestToolDescriptor[];
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Execute a tool on the remote server via the execute_tool meta-tool.
|
|
23
|
+
* Validates args locally first if schema is cached.
|
|
24
|
+
*/
|
|
25
|
+
executeTool(toolName: string, args: Record<string, unknown>): Promise<ToolExecutionResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Lightweight fetch of connected toolkit metadata only (no tool schemas).
|
|
28
|
+
* Used by the cache for background refresh.
|
|
29
|
+
*/
|
|
30
|
+
listToolkits(): Promise<ToolforestToolkit[]>;
|
|
31
|
+
disconnect(): Promise<void>;
|
|
32
|
+
private parseJsonResponse;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EACV,wBAAwB,EACxB,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAGpB,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,SAAS,CAA8C;IAE/D,sDAAsD;IACtD,OAAO,CAAC,WAAW,CAA8C;IAE3D,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAezD;;;;OAIG;IACG,aAAa,IAAI,OAAO,CAAC;QAC7B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;QAC9B,KAAK,EAAE,wBAAwB,EAAE,CAAC;KACnC,CAAC;IAqCF;;;OAGG;IACG,WAAW,CACf,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,mBAAmB,CAAC;IA0B/B;;;OAGG;IACG,YAAY,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAM5C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAYjC,OAAO,CAAC,iBAAiB;CAU1B"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolforest MCP client — connects to the remote Toolforest MCP server,
|
|
3
|
+
* discovers all connected tools, and forwards execution requests.
|
|
4
|
+
*/
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7
|
+
import { validateArgs } from "./validate.js";
|
|
8
|
+
export class ToolforestClient {
|
|
9
|
+
client = null;
|
|
10
|
+
transport = null;
|
|
11
|
+
/** Cached tool schemas for client-side validation. */
|
|
12
|
+
schemaCache = new Map();
|
|
13
|
+
async connect(url, apiKey) {
|
|
14
|
+
this.transport = new StreamableHTTPClientTransport(new URL(url), {
|
|
15
|
+
requestInit: {
|
|
16
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
this.client = new Client({ name: "@toolforest/openclaw-plugin", version: "0.1.0" }, { capabilities: {} });
|
|
20
|
+
await this.client.connect(this.transport);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Discover all connected tools by calling the router meta-tools.
|
|
24
|
+
* 1. list_tools() → get connected toolkit names
|
|
25
|
+
* 2. list_tools(toolkit=X) for each → get tool descriptors with schemas
|
|
26
|
+
*/
|
|
27
|
+
async discoverTools() {
|
|
28
|
+
if (!this.client)
|
|
29
|
+
throw new Error("Not connected");
|
|
30
|
+
// Step 1: Get connected toolkits
|
|
31
|
+
const toolkitResult = await this.client.callTool({
|
|
32
|
+
name: "list_tools",
|
|
33
|
+
arguments: {},
|
|
34
|
+
});
|
|
35
|
+
const toolkits = this.parseJsonResponse(toolkitResult) ?? [];
|
|
36
|
+
// Step 2: Get tools for each toolkit (parallel)
|
|
37
|
+
const allTools = [];
|
|
38
|
+
const results = await Promise.allSettled(toolkits.map(async (toolkit) => {
|
|
39
|
+
const result = await this.client.callTool({
|
|
40
|
+
name: "list_tools",
|
|
41
|
+
arguments: { toolkit: toolkit.name },
|
|
42
|
+
});
|
|
43
|
+
return this.parseJsonResponse(result) ?? [];
|
|
44
|
+
}));
|
|
45
|
+
for (const result of results) {
|
|
46
|
+
if (result.status === "fulfilled") {
|
|
47
|
+
for (const tool of result.value) {
|
|
48
|
+
allTools.push(tool);
|
|
49
|
+
// Cache schema for validation
|
|
50
|
+
if (tool.schema) {
|
|
51
|
+
this.schemaCache.set(tool.name, tool.schema);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { toolkits, tools: allTools };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Execute a tool on the remote server via the execute_tool meta-tool.
|
|
60
|
+
* Validates args locally first if schema is cached.
|
|
61
|
+
*/
|
|
62
|
+
async executeTool(toolName, args) {
|
|
63
|
+
if (!this.client)
|
|
64
|
+
throw new Error("Not connected");
|
|
65
|
+
// Client-side validation
|
|
66
|
+
const schema = this.schemaCache.get(toolName);
|
|
67
|
+
if (schema) {
|
|
68
|
+
const error = validateArgs(args, schema);
|
|
69
|
+
if (error) {
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: "text", text: `Validation error: ${error}` }],
|
|
72
|
+
isError: true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const result = await this.client.callTool({
|
|
77
|
+
name: "execute_tool",
|
|
78
|
+
arguments: { tool_name: toolName, args },
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
content: (result.content ?? []),
|
|
82
|
+
isError: result.isError,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Lightweight fetch of connected toolkit metadata only (no tool schemas).
|
|
87
|
+
* Used by the cache for background refresh.
|
|
88
|
+
*/
|
|
89
|
+
async listToolkits() {
|
|
90
|
+
if (!this.client)
|
|
91
|
+
throw new Error("Not connected");
|
|
92
|
+
const result = await this.client.callTool({ name: "list_tools", arguments: {} });
|
|
93
|
+
return this.parseJsonResponse(result) ?? [];
|
|
94
|
+
}
|
|
95
|
+
async disconnect() {
|
|
96
|
+
if (this.client) {
|
|
97
|
+
try {
|
|
98
|
+
await this.client.close();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Ignore close errors
|
|
102
|
+
}
|
|
103
|
+
this.client = null;
|
|
104
|
+
}
|
|
105
|
+
this.transport = null;
|
|
106
|
+
}
|
|
107
|
+
parseJsonResponse(result) {
|
|
108
|
+
const content = result.content;
|
|
109
|
+
const textBlock = content?.find((c) => c.type === "text" && c.text);
|
|
110
|
+
if (!textBlock?.text)
|
|
111
|
+
return null;
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(textBlock.text);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve Toolforest plugin configuration from plugin config, env vars, and defaults.
|
|
3
|
+
*/
|
|
4
|
+
import type { ToolforestPluginConfig } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the Toolforest connection config.
|
|
7
|
+
* Priority: pluginConfig > env vars > defaults.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveConfig(pluginConfig: Record<string, unknown> | undefined): ToolforestPluginConfig;
|
|
10
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAIzD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAChD,sBAAsB,CAcxB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve Toolforest plugin configuration from plugin config, env vars, and defaults.
|
|
3
|
+
*/
|
|
4
|
+
const DEFAULT_URL = "https://mcp.toolforest.io/mcp";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the Toolforest connection config.
|
|
7
|
+
* Priority: pluginConfig > env vars > defaults.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveConfig(pluginConfig) {
|
|
10
|
+
const cfg = pluginConfig ?? {};
|
|
11
|
+
const apiKey = cfg.apiKey ??
|
|
12
|
+
process.env.TOOLFOREST_API_KEY ??
|
|
13
|
+
"";
|
|
14
|
+
const remoteUrl = cfg.remoteUrl ??
|
|
15
|
+
process.env.TOOLFOREST_URL ??
|
|
16
|
+
DEFAULT_URL;
|
|
17
|
+
return { apiKey, remoteUrl };
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,WAAW,GAAG,+BAA+B,CAAC;AAEpD;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,YAAiD;IAEjD,MAAM,GAAG,GAAG,YAAY,IAAI,EAAE,CAAC;IAE/B,MAAM,MAAM,GACT,GAAG,CAAC,MAA6B;QAClC,OAAO,CAAC,GAAG,CAAC,kBAAkB;QAC9B,EAAE,CAAC;IAEL,MAAM,SAAS,GACZ,GAAG,CAAC,SAAgC;QACrC,OAAO,CAAC,GAAG,CAAC,cAAc;QAC1B,WAAW,CAAC;IAEd,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PromptState } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build the <toolforest> prompt block based on current plugin state.
|
|
4
|
+
* Injected via the before_prompt_build hook on every agent turn.
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildBlock(state: PromptState): string;
|
|
7
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAiCrD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the <toolforest> prompt block based on current plugin state.
|
|
3
|
+
* Injected via the before_prompt_build hook on every agent turn.
|
|
4
|
+
*/
|
|
5
|
+
export function buildBlock(state) {
|
|
6
|
+
if (state.status === "error") {
|
|
7
|
+
return `<toolforest>
|
|
8
|
+
Toolforest failed to connect.${state.message ? ` Error: ${state.message}` : ""}
|
|
9
|
+
|
|
10
|
+
Tell the user:
|
|
11
|
+
"Toolforest isn't connected. To fix:
|
|
12
|
+
1. Get your API key from www.toolforest.io
|
|
13
|
+
2. Run: openclaw config set plugins.entries.toolforest.config.apiKey \\"your_key\\"
|
|
14
|
+
3. Run: openclaw gateway restart"
|
|
15
|
+
|
|
16
|
+
Do NOT fabricate tool names.
|
|
17
|
+
</toolforest>`;
|
|
18
|
+
}
|
|
19
|
+
const toolkitLines = state.toolkits
|
|
20
|
+
.map((t) => ` - ${t.name}: ${t.description ?? "(no description)"}`)
|
|
21
|
+
.join("\n");
|
|
22
|
+
return `<toolforest>
|
|
23
|
+
You have ${state.toolCount} Toolforest tools available, prefixed with "toolforest_".
|
|
24
|
+
|
|
25
|
+
Connected toolkits:
|
|
26
|
+
${toolkitLines}
|
|
27
|
+
|
|
28
|
+
Rules:
|
|
29
|
+
- Use Toolforest tools for tasks involving the connected services above.
|
|
30
|
+
- Call tools directly by name — no discovery step needed.
|
|
31
|
+
- Do NOT fabricate tool names not listed above.
|
|
32
|
+
- Do NOT use pretrained knowledge about Toolforest APIs.
|
|
33
|
+
- If a tool returns an auth error, direct the user to www.toolforest.io to reconnect.
|
|
34
|
+
- Your API key is at plugins.entries.toolforest.config.apiKey in openclaw.json if needed.
|
|
35
|
+
</toolforest>`;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/prompt.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,KAAkB;IAC3C,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAC7B,OAAO;+BACoB,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE;;;;;;;;;cAShE,CAAC;IACb,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ;SAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,WAAW,IAAI,kBAAkB,EAAE,CAAC;SACnE,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;WACE,KAAK,CAAC,SAAS;;;EAGxB,YAAY;;;;;;;;;cASA,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge Toolforest tool descriptors into OpenClaw AnyAgentTool format.
|
|
3
|
+
*
|
|
4
|
+
* Follows the pattern from openclaw/src/agents/pi-bundle-mcp-tools.ts:
|
|
5
|
+
* - parameters: raw JSON Schema (TypeBox compiles to JSON Schema at runtime)
|
|
6
|
+
* - execute: forward to remote server, return { content, details }
|
|
7
|
+
*/
|
|
8
|
+
import type { ToolforestClient } from "./client.js";
|
|
9
|
+
import type { ToolforestToolDescriptor } from "./types.js";
|
|
10
|
+
/** The AnyAgentTool shape OpenClaw expects (from pi-agent-core). */
|
|
11
|
+
export interface BridgedTool {
|
|
12
|
+
name: string;
|
|
13
|
+
label: string;
|
|
14
|
+
description: string;
|
|
15
|
+
parameters: Record<string, unknown>;
|
|
16
|
+
execute: (toolCallId: string, params: Record<string, unknown>) => Promise<{
|
|
17
|
+
content: Array<{
|
|
18
|
+
type: string;
|
|
19
|
+
text?: string;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}>;
|
|
22
|
+
details?: Record<string, unknown>;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Convert a Toolforest tool descriptor into an OpenClaw-compatible tool object.
|
|
27
|
+
*/
|
|
28
|
+
export declare function bridgeTool(descriptor: ToolforestToolDescriptor, client: ToolforestClient): BridgedTool;
|
|
29
|
+
/**
|
|
30
|
+
* Bridge all Toolforest tool descriptors into OpenClaw tools.
|
|
31
|
+
*/
|
|
32
|
+
export declare function bridgeAllTools(descriptors: ToolforestToolDescriptor[], client: ToolforestClient): BridgedTool[];
|
|
33
|
+
//# sourceMappingURL=tool-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-bridge.d.ts","sourceRoot":"","sources":["../../src/tool-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,OAAO,EAAE,CACP,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC;QACX,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAC;QACxE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACnC,CAAC,CAAC;CACJ;AA6BD;;GAEG;AACH,wBAAgB,UAAU,CACxB,UAAU,EAAE,wBAAwB,EACpC,MAAM,EAAE,gBAAgB,GACvB,WAAW,CAmBb;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,wBAAwB,EAAE,EACvC,MAAM,EAAE,gBAAgB,GACvB,WAAW,EAAE,CAEf"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge Toolforest tool descriptors into OpenClaw AnyAgentTool format.
|
|
3
|
+
*
|
|
4
|
+
* Follows the pattern from openclaw/src/agents/pi-bundle-mcp-tools.ts:
|
|
5
|
+
* - parameters: raw JSON Schema (TypeBox compiles to JSON Schema at runtime)
|
|
6
|
+
* - execute: forward to remote server, return { content, details }
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Normalize a Toolforest tool name for OpenClaw.
|
|
10
|
+
* "github-list_repos" → "toolforest_github_list_repos"
|
|
11
|
+
*/
|
|
12
|
+
function normalizeName(toolforestName) {
|
|
13
|
+
return `toolforest_${toolforestName.replace(/-/g, "_")}`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generate a human-readable label from toolkit and tool names.
|
|
17
|
+
* "github-list_repos" → "GitHub: List Repos"
|
|
18
|
+
*/
|
|
19
|
+
function generateLabel(toolforestName) {
|
|
20
|
+
const parts = toolforestName.split("-");
|
|
21
|
+
const toolkit = parts[0]
|
|
22
|
+
.split("_")
|
|
23
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
24
|
+
.join(" ");
|
|
25
|
+
const action = (parts.slice(1).join("-") || parts[0])
|
|
26
|
+
.split("_")
|
|
27
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
28
|
+
.join(" ");
|
|
29
|
+
return `${toolkit}: ${action}`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Convert a Toolforest tool descriptor into an OpenClaw-compatible tool object.
|
|
33
|
+
*/
|
|
34
|
+
export function bridgeTool(descriptor, client) {
|
|
35
|
+
const originalName = descriptor.name;
|
|
36
|
+
return {
|
|
37
|
+
name: normalizeName(originalName),
|
|
38
|
+
label: generateLabel(originalName),
|
|
39
|
+
description: descriptor.summary || `Toolforest tool: ${originalName}`,
|
|
40
|
+
parameters: descriptor.schema,
|
|
41
|
+
execute: async (_toolCallId, params) => {
|
|
42
|
+
const result = await client.executeTool(originalName, params);
|
|
43
|
+
return {
|
|
44
|
+
content: result.content,
|
|
45
|
+
details: {
|
|
46
|
+
toolforestTool: originalName,
|
|
47
|
+
...(result.isError ? { status: "error" } : {}),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Bridge all Toolforest tool descriptors into OpenClaw tools.
|
|
55
|
+
*/
|
|
56
|
+
export function bridgeAllTools(descriptors, client) {
|
|
57
|
+
return descriptors.map((d) => bridgeTool(d, client));
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=tool-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-bridge.js","sourceRoot":"","sources":["../../src/tool-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAoBH;;;GAGG;AACH,SAAS,aAAa,CAAC,cAAsB;IAC3C,OAAO,cAAc,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,cAAsB;IAC3C,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC;SACrB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAClD,IAAI,CAAC,GAAG,CAAC,CAAC;IAEb,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;SAClD,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAClD,IAAI,CAAC,GAAG,CAAC,CAAC;IAEb,OAAO,GAAG,OAAO,KAAK,MAAM,EAAE,CAAC;AACjC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,UAAoC,EACpC,MAAwB;IAExB,MAAM,YAAY,GAAG,UAAU,CAAC,IAAI,CAAC;IAErC,OAAO;QACL,IAAI,EAAE,aAAa,CAAC,YAAY,CAAC;QACjC,KAAK,EAAE,aAAa,CAAC,YAAY,CAAC;QAClC,WAAW,EAAE,UAAU,CAAC,OAAO,IAAI,oBAAoB,YAAY,EAAE;QACrE,UAAU,EAAE,UAAU,CAAC,MAAM;QAC7B,OAAO,EAAE,KAAK,EAAE,WAAmB,EAAE,MAA+B,EAAE,EAAE;YACtE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;YAC9D,OAAO;gBACL,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,OAAO,EAAE;oBACP,cAAc,EAAE,YAAY;oBAC5B,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC/C;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,WAAuC,EACvC,MAAwB;IAExB,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ToolforestClient } from "./client.js";
|
|
2
|
+
import type { Logger, ToolforestToolkit } from "./types.js";
|
|
3
|
+
export declare class ToolkitCache {
|
|
4
|
+
private client;
|
|
5
|
+
private logger;
|
|
6
|
+
private cachedToolkits;
|
|
7
|
+
private cachedAt;
|
|
8
|
+
private fetchPromise;
|
|
9
|
+
constructor(client: ToolforestClient, logger: Logger);
|
|
10
|
+
/** Seed with initial data from startup discovery. */
|
|
11
|
+
seed(toolkits: ToolforestToolkit[]): void;
|
|
12
|
+
/**
|
|
13
|
+
* Return current cached toolkits. If stale, triggers a background refresh
|
|
14
|
+
* (current turn uses stale data, next turn gets fresh data).
|
|
15
|
+
*/
|
|
16
|
+
getToolkits(): ToolforestToolkit[];
|
|
17
|
+
private refreshInBackground;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=toolkit-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolkit-cache.d.ts","sourceRoot":"","sources":["../../src/toolkit-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAI5D,qBAAa,YAAY;IAMrB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IANhB,OAAO,CAAC,cAAc,CAA2B;IACjD,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,YAAY,CAA8B;gBAGxC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,MAAM;IAGxB,qDAAqD;IACrD,IAAI,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAKzC;;;OAGG;IACH,WAAW,IAAI,iBAAiB,EAAE;IAOlC,OAAO,CAAC,mBAAmB;CAwB5B"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
2
|
+
export class ToolkitCache {
|
|
3
|
+
client;
|
|
4
|
+
logger;
|
|
5
|
+
cachedToolkits = [];
|
|
6
|
+
cachedAt = 0;
|
|
7
|
+
fetchPromise = null;
|
|
8
|
+
constructor(client, logger) {
|
|
9
|
+
this.client = client;
|
|
10
|
+
this.logger = logger;
|
|
11
|
+
}
|
|
12
|
+
/** Seed with initial data from startup discovery. */
|
|
13
|
+
seed(toolkits) {
|
|
14
|
+
this.cachedToolkits = toolkits;
|
|
15
|
+
this.cachedAt = Date.now();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Return current cached toolkits. If stale, triggers a background refresh
|
|
19
|
+
* (current turn uses stale data, next turn gets fresh data).
|
|
20
|
+
*/
|
|
21
|
+
getToolkits() {
|
|
22
|
+
if (Date.now() - this.cachedAt > CACHE_TTL_MS) {
|
|
23
|
+
this.refreshInBackground();
|
|
24
|
+
}
|
|
25
|
+
return this.cachedToolkits;
|
|
26
|
+
}
|
|
27
|
+
refreshInBackground() {
|
|
28
|
+
// In-flight deduplication: only one fetch at a time
|
|
29
|
+
if (this.fetchPromise)
|
|
30
|
+
return;
|
|
31
|
+
this.fetchPromise = this.client
|
|
32
|
+
.listToolkits()
|
|
33
|
+
.then((toolkits) => {
|
|
34
|
+
this.cachedToolkits = toolkits;
|
|
35
|
+
this.cachedAt = Date.now();
|
|
36
|
+
this.logger.debug(`toolforest: Refreshed toolkit cache (${toolkits.length} toolkits)`);
|
|
37
|
+
})
|
|
38
|
+
.catch((err) => {
|
|
39
|
+
// Stale data persists — don't hammer on errors
|
|
40
|
+
this.cachedAt = Date.now();
|
|
41
|
+
this.logger.warn(`toolforest: Background toolkit refresh failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
42
|
+
})
|
|
43
|
+
.finally(() => {
|
|
44
|
+
this.fetchPromise = null;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=toolkit-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolkit-cache.js","sourceRoot":"","sources":["../../src/toolkit-cache.ts"],"names":[],"mappings":"AAGA,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,YAAY;AAEhD,MAAM,OAAO,YAAY;IAMb;IACA;IANF,cAAc,GAAwB,EAAE,CAAC;IACzC,QAAQ,GAAG,CAAC,CAAC;IACb,YAAY,GAAyB,IAAI,CAAC;IAElD,YACU,MAAwB,EACxB,MAAc;QADd,WAAM,GAAN,MAAM,CAAkB;QACxB,WAAM,GAAN,MAAM,CAAQ;IACrB,CAAC;IAEJ,qDAAqD;IACrD,IAAI,CAAC,QAA6B;QAChC,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,WAAW;QACT,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,GAAG,YAAY,EAAE,CAAC;YAC9C,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC7B,CAAC;QACD,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAEO,mBAAmB;QACzB,oDAAoD;QACpD,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO;QAE9B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,MAAM;aAC5B,YAAY,EAAE;aACd,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;YACjB,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC;YAC/B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,wCAAwC,QAAQ,CAAC,MAAM,YAAY,CACpE,CAAC;QACJ,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,+CAA+C;YAC/C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,kDAAkD,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACrG,CAAC;QACJ,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3B,CAAC,CAAC,CAAC;IACP,CAAC;CACF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for the Toolforest OpenClaw plugin.
|
|
3
|
+
*/
|
|
4
|
+
/** Tool descriptor returned by Toolforest list_tools(toolkit=X). */
|
|
5
|
+
export interface ToolforestToolDescriptor {
|
|
6
|
+
name: string;
|
|
7
|
+
summary: string;
|
|
8
|
+
kind: string;
|
|
9
|
+
schema: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
/** Toolkit descriptor returned by Toolforest list_tools() (no args). */
|
|
12
|
+
export interface ToolforestToolkit {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
}
|
|
16
|
+
/** Resolved plugin configuration. */
|
|
17
|
+
export interface ToolforestPluginConfig {
|
|
18
|
+
apiKey: string;
|
|
19
|
+
remoteUrl: string;
|
|
20
|
+
}
|
|
21
|
+
/** Result from executing a tool via the MCP server. */
|
|
22
|
+
export interface ToolExecutionResult {
|
|
23
|
+
content: Array<{
|
|
24
|
+
type: string;
|
|
25
|
+
text?: string;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}>;
|
|
28
|
+
isError?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Logger interface (subset of PluginApi.logger). */
|
|
31
|
+
export interface Logger {
|
|
32
|
+
info(msg: string): void;
|
|
33
|
+
warn(msg: string): void;
|
|
34
|
+
error(msg: string): void;
|
|
35
|
+
debug(msg: string): void;
|
|
36
|
+
}
|
|
37
|
+
/** Prompt state for the before_prompt_build hook. */
|
|
38
|
+
export type PromptState = {
|
|
39
|
+
status: "error";
|
|
40
|
+
message: string;
|
|
41
|
+
} | {
|
|
42
|
+
status: "ready";
|
|
43
|
+
toolkits: ToolforestToolkit[];
|
|
44
|
+
toolCount: number;
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oEAAoE;AACpE,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,wEAAwE;AACxE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qCAAqC;AACrC,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,uDAAuD;AACvD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qDAAqD;AACrD,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,qDAAqD;AACrD,MAAM,MAAM,WAAW,GACnB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight JSON Schema validator for tool arguments.
|
|
3
|
+
*
|
|
4
|
+
* Catches common mistakes (missing required fields, wrong types, unknown params)
|
|
5
|
+
* before making the network round-trip. The remote server does full jsonschema
|
|
6
|
+
* validation as a second layer.
|
|
7
|
+
*
|
|
8
|
+
* Inlined from @toolforest/mcp-router to avoid cross-package dependency.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Validate args against a JSON Schema. Returns null if valid, or an error message.
|
|
12
|
+
*/
|
|
13
|
+
export declare function validateArgs(args: Record<string, unknown>, schema: Record<string, unknown>): string | null;
|
|
14
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,GAAG,IAAI,CAuCf"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight JSON Schema validator for tool arguments.
|
|
3
|
+
*
|
|
4
|
+
* Catches common mistakes (missing required fields, wrong types, unknown params)
|
|
5
|
+
* before making the network round-trip. The remote server does full jsonschema
|
|
6
|
+
* validation as a second layer.
|
|
7
|
+
*
|
|
8
|
+
* Inlined from @toolforest/mcp-router to avoid cross-package dependency.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Validate args against a JSON Schema. Returns null if valid, or an error message.
|
|
12
|
+
*/
|
|
13
|
+
export function validateArgs(args, schema) {
|
|
14
|
+
const properties = schema.properties;
|
|
15
|
+
const required = schema.required;
|
|
16
|
+
// Check required fields
|
|
17
|
+
if (required) {
|
|
18
|
+
for (const field of required) {
|
|
19
|
+
if (!(field in args) || args[field] === undefined) {
|
|
20
|
+
return `Missing required parameter: '${field}'`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Check types and constraints of provided fields
|
|
25
|
+
if (properties) {
|
|
26
|
+
for (const [key, value] of Object.entries(args)) {
|
|
27
|
+
const propSchema = properties[key];
|
|
28
|
+
if (!propSchema) {
|
|
29
|
+
if (schema.additionalProperties === false) {
|
|
30
|
+
return `Unknown parameter: '${key}'`;
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const expectedType = propSchema.type;
|
|
35
|
+
if (expectedType && value !== null) {
|
|
36
|
+
const typeErr = checkType(value, expectedType);
|
|
37
|
+
if (typeErr)
|
|
38
|
+
return `Parameter '${key}': ${typeErr}`;
|
|
39
|
+
}
|
|
40
|
+
// Enum constraint
|
|
41
|
+
const enumValues = propSchema.enum;
|
|
42
|
+
if (enumValues && !enumValues.includes(value)) {
|
|
43
|
+
return `Parameter '${key}': must be one of: ${enumValues.map(String).join(', ')}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function checkType(value, expectedType) {
|
|
50
|
+
const types = Array.isArray(expectedType) ? expectedType : [expectedType];
|
|
51
|
+
for (const t of types) {
|
|
52
|
+
switch (t) {
|
|
53
|
+
case 'string':
|
|
54
|
+
if (typeof value === 'string')
|
|
55
|
+
return null;
|
|
56
|
+
break;
|
|
57
|
+
case 'number':
|
|
58
|
+
case 'integer':
|
|
59
|
+
if (typeof value === 'number')
|
|
60
|
+
return null;
|
|
61
|
+
break;
|
|
62
|
+
case 'boolean':
|
|
63
|
+
if (typeof value === 'boolean')
|
|
64
|
+
return null;
|
|
65
|
+
break;
|
|
66
|
+
case 'array':
|
|
67
|
+
if (Array.isArray(value))
|
|
68
|
+
return null;
|
|
69
|
+
break;
|
|
70
|
+
case 'object':
|
|
71
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value))
|
|
72
|
+
return null;
|
|
73
|
+
break;
|
|
74
|
+
case 'null':
|
|
75
|
+
if (value === null)
|
|
76
|
+
return null;
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
return null; // Unknown type keyword — pass through
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const got = Array.isArray(value) ? 'array' : typeof value;
|
|
83
|
+
return `expected ${types.join(' | ')}, got ${got}`;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "toolforest",
|
|
3
|
+
"name": "Toolforest",
|
|
4
|
+
"description": "Connect all your Toolforest toolkits as native OpenClaw agent tools. Supports 250+ tools across GitHub, Google Sheets, Slack, and more.",
|
|
5
|
+
"configSchema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"apiKey": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Toolforest API key (tfo_...)"
|
|
12
|
+
},
|
|
13
|
+
"remoteUrl": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Override the Toolforest MCP endpoint URL"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"skills": ["./skills/toolforest-mcp"],
|
|
20
|
+
"uiHints": {
|
|
21
|
+
"apiKey": {
|
|
22
|
+
"label": "Toolforest API Key",
|
|
23
|
+
"help": "Your Toolforest API key (starts with tfo_). Get one at app.toolforest.io. Fallback: TOOLFOREST_API_KEY env var.",
|
|
24
|
+
"sensitive": true,
|
|
25
|
+
"placeholder": "tfo_..."
|
|
26
|
+
},
|
|
27
|
+
"remoteUrl": {
|
|
28
|
+
"label": "Remote URL",
|
|
29
|
+
"help": "Override the default Toolforest MCP endpoint URL. Leave blank to use the standard URL for your environment.",
|
|
30
|
+
"advanced": true
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@toolforest/toolforest-plugin",
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "OpenClaw plugin for Toolforest — registers all connected tools as native agent tools",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"openclaw.plugin.json",
|
|
11
|
+
"skills",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.11.1"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"openclaw": ">=2026.3.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependenciesMeta": {
|
|
26
|
+
"openclaw": {
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"openclaw": {
|
|
31
|
+
"extensions": [
|
|
32
|
+
"./dist/index.js"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"registry": "https://registry.npmjs.org",
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20.0.0"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"openclaw",
|
|
44
|
+
"openclaw-plugin",
|
|
45
|
+
"toolforest",
|
|
46
|
+
"mcp",
|
|
47
|
+
"ai-tools"
|
|
48
|
+
],
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/Toolforest-io/toolforest_testing.git",
|
|
53
|
+
"directory": "packages/openclaw-plugin"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: toolforest-mcp
|
|
3
|
+
description: Use the Toolforest MCP server directly to call connected toolkit tools when the plugin tools are unavailable or not working.
|
|
4
|
+
metadata:
|
|
5
|
+
{ "openclaw": { "emoji": "🌲", "homepage": "https://www.toolforest.io", "requires": { "bins": ["curl"] } } }
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Toolforest MCP Fallback
|
|
9
|
+
|
|
10
|
+
## API Key
|
|
11
|
+
|
|
12
|
+
First check the openclaw config:
|
|
13
|
+
```bash
|
|
14
|
+
grep -A5 '"toolforest"' ~/.openclaw/openclaw.json
|
|
15
|
+
```
|
|
16
|
+
Key is at `plugins.entries.toolforest.config.apiKey`. If missing, direct user to **www.toolforest.io**.
|
|
17
|
+
|
|
18
|
+
## MCP Server
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
https://mcp.toolforest.io/mcp
|
|
22
|
+
Authorization: Bearer <apiKey>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Discover toolkits
|
|
26
|
+
```bash
|
|
27
|
+
curl -s -X POST https://mcp.toolforest.io/mcp \
|
|
28
|
+
-H "Content-Type: application/json" \
|
|
29
|
+
-H "Authorization: Bearer YOUR_KEY" \
|
|
30
|
+
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"list_tools","arguments":{}}}'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Execute a tool
|
|
34
|
+
```bash
|
|
35
|
+
curl -s -X POST https://mcp.toolforest.io/mcp \
|
|
36
|
+
-H "Content-Type: application/json" \
|
|
37
|
+
-H "Authorization: Bearer YOUR_KEY" \
|
|
38
|
+
-d '{
|
|
39
|
+
"jsonrpc": "2.0", "id": "1", "method": "tools/call",
|
|
40
|
+
"params": { "name": "execute_tool", "arguments": { "tool_name": "TOOL_NAME", "args": {} } }
|
|
41
|
+
}'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Responses may be SSE (`data: {...}`) or plain JSON — handle both.
|