@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 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
@@ -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,5 @@
1
+ /**
2
+ * Shared type definitions for the Toolforest OpenClaw plugin.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -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.