opencode-toolbox 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,6 +46,7 @@ Create `~/.config/opencode/toolbox.jsonc`:
46
46
 
47
47
  ```jsonc
48
48
  {
49
+ "$schema": "https://unpkg.com/opencode-toolbox@latest/toolbox.schema.json",
49
50
  "mcp": {
50
51
  "time": {
51
52
  "type": "local",
@@ -72,6 +73,8 @@ Create `~/.config/opencode/toolbox.jsonc`:
72
73
  }
73
74
  ```
74
75
 
76
+ > **Note:** The config file is auto-created with default settings if it doesn't exist.
77
+
75
78
  ### Environment Variables
76
79
 
77
80
  - `OPENCODE_TOOLBOX_CONFIG`: Path to config file (default: `~/.config/opencode/toolbox.jsonc`)
@@ -165,6 +168,9 @@ Returns a comprehensive status object:
165
168
  {
166
169
  "plugin": {
167
170
  "initialized": true,
171
+ "initState": "ready",
172
+ "initMode": "eager",
173
+ "initDurationMs": 1234,
168
174
  "configPath": "/Users/username/.config/opencode/toolbox.jsonc",
169
175
  "uptime": 123.45,
170
176
  "searches": 23,
@@ -182,8 +188,9 @@ Returns a comprehensive status object:
182
188
  "name": "time",
183
189
  "status": "connected",
184
190
  "type": "local",
185
- "toolCount": 5,
191
+ "toolCount": 2,
186
192
  "error": null,
193
+ "commandString": "uvx mcp-server-time",
187
194
  "healthy": true
188
195
  },
189
196
  {
@@ -192,6 +199,7 @@ Returns a comprehensive status object:
192
199
  "type": "local",
193
200
  "toolCount": 12,
194
201
  "error": null,
202
+ "commandString": "npx -y @anthropic/mcp-github",
195
203
  "healthy": true
196
204
  },
197
205
  {
@@ -199,16 +207,25 @@ Returns a comprehensive status object:
199
207
  "status": "error",
200
208
  "type": "remote",
201
209
  "toolCount": 0,
202
- "error": "Failed to connect: timeout",
210
+ "error": "Connection timeout after 5000ms",
211
+ "url": "https://mcp.example.com/weather",
203
212
  "healthy": false
204
213
  }
205
214
  ]
206
215
  },
207
216
  "tools": {
208
- "total": 17,
209
- "available": 17,
217
+ "total": 14,
218
+ "indexed": 14,
210
219
  "serversWithTools": 2
211
220
  },
221
+ "toolboxTools": [
222
+ "toolbox_search_bm25",
223
+ "toolbox_search_regex",
224
+ "toolbox_execute",
225
+ "toolbox_status",
226
+ "toolbox_perf",
227
+ "toolbox_test"
228
+ ],
212
229
  "health": {
213
230
  "status": "degraded",
214
231
  "message": "1 server(s) failed to connect"
@@ -223,7 +240,7 @@ Returns a comprehensive status object:
223
240
 
224
241
  ### /toolbox-status Slash Command
225
242
 
226
- The plugin automatically creates a `/toolbox-status` slash command on first launch:
243
+ The plugin automatically creates and maintains the `/toolbox-status` slash command:
227
244
 
228
245
  ```
229
246
  ~/.config/opencode/command/toolbox-status.md
@@ -231,6 +248,8 @@ The plugin automatically creates a `/toolbox-status` slash command on first laun
231
248
 
232
249
  Use it in OpenCode by typing `/toolbox-status` to get a formatted status report.
233
250
 
251
+ > **Note:** The command file auto-updates when the plugin version changes.
252
+
234
253
  ### toolbox_perf
235
254
 
236
255
  Get detailed performance metrics for the toolbox plugin:
@@ -337,8 +356,10 @@ grep "WARN" ~/.local/share/opencode/toolbox.log
337
356
  **Log format:**
338
357
  ```
339
358
  2026-01-08T12:34:56.789Z [INFO] Toolbox plugin loaded successfully {"configPath":"...","serverCount":6}
340
- 2026-01-08T12:34:57.123Z [INFO] Initialization complete: 5/6 servers connected, 42 tools indexed
341
- 2026-01-08T12:34:57.124Z [WARN] 1 server(s) failed to connect: weather
359
+ 2026-01-08T12:34:57.100Z [INFO] time - connection time: 648.12ms, indexed 2 tools in 0.07ms
360
+ 2026-01-08T12:34:57.200Z [INFO] github - connection time: 892.45ms, indexed 12 tools in 0.15ms
361
+ 2026-01-08T12:34:58.500Z [INFO] Initialization complete in 1723.45ms: 2/3 servers, 14 tools indexed
362
+ 2026-01-08T12:34:58.501Z [WARN] Server weather failed: Connection timeout after 5000ms
342
363
  2026-01-08T12:35:00.456Z [INFO] BM25 search completed: "web search" -> 3 results
343
364
  ```
344
365
 
@@ -381,6 +402,8 @@ This shows:
381
402
  4. For remote servers, verify URL is accessible
382
403
  5. Check environment variables are set correctly
383
404
 
405
+ > **Note:** Connection retries use exponential backoff (100ms → 200ms → 400ms..., max 30s) before failing.
406
+
384
407
  ### Execute fails
385
408
 
386
409
  1. Run `toolbox_status({})` to check server health
package/dist/index.js CHANGED
@@ -19267,7 +19267,7 @@ function tool(input) {
19267
19267
  }
19268
19268
  tool.schema = exports_external;
19269
19269
  // src/plugin.ts
19270
- import { appendFile, mkdir, writeFile, access } from "fs/promises";
19270
+ import { appendFile, mkdir as mkdir2, writeFile as writeFile2, readFile } from "fs/promises";
19271
19271
 
19272
19272
  // node_modules/zod/v4/classic/external.js
19273
19273
  var exports_external2 = {};
@@ -32812,10 +32812,14 @@ var RemoteServerConfigSchema = exports_external2.object({
32812
32812
  url: exports_external2.string().url().describe("Remote MCP endpoint URL"),
32813
32813
  headers: exports_external2.record(exports_external2.string(), exports_external2.string()).optional().describe("HTTP headers for authentication")
32814
32814
  });
32815
- var ServerConfigSchema = exports_external2.discriminatedUnion("type", [
32815
+ var ServerConfigSchema = exports_external2.object({
32816
+ type: exports_external2.enum(["local", "remote"], {
32817
+ error: 'Server "type" must be "local" or "remote"'
32818
+ })
32819
+ }).passthrough().pipe(exports_external2.discriminatedUnion("type", [
32816
32820
  LocalServerConfigSchema,
32817
32821
  RemoteServerConfigSchema
32818
- ]);
32822
+ ]));
32819
32823
  var ConnectionConfigSchema = exports_external2.object({
32820
32824
  connectTimeout: exports_external2.number().min(100).max(60000).default(5000),
32821
32825
  requestTimeout: exports_external2.number().min(100).max(300000).default(30000),
@@ -32828,6 +32832,7 @@ var SettingsConfigSchema = exports_external2.object({
32828
32832
  connection: ConnectionConfigSchema.optional()
32829
32833
  });
32830
32834
  var ConfigSchema = exports_external2.object({
32835
+ $schema: exports_external2.string().optional(),
32831
32836
  mcp: exports_external2.record(exports_external2.string(), ServerConfigSchema),
32832
32837
  settings: SettingsConfigSchema.optional()
32833
32838
  });
@@ -33636,6 +33641,47 @@ var ParseErrorCode;
33636
33641
  })(ParseErrorCode || (ParseErrorCode = {}));
33637
33642
 
33638
33643
  // src/config/loader.ts
33644
+ import { mkdir, writeFile } from "fs/promises";
33645
+ import { dirname } from "path";
33646
+ var NPM_PACKAGE = "opencode-toolbox";
33647
+ function getSchemaUrl(_version) {
33648
+ return `https://unpkg.com/${NPM_PACKAGE}@latest/toolbox.schema.json`;
33649
+ }
33650
+ function generateDefaultConfig(version3) {
33651
+ const schemaUrl = getSchemaUrl(version3);
33652
+ return `{
33653
+ "$schema": "${schemaUrl}",
33654
+ "mcp": {
33655
+ // Add your MCP servers here
33656
+ // Example:
33657
+ // "time": {
33658
+ // "type": "local",
33659
+ // "command": ["npx", "-y", "@anthropic/mcp-time"]
33660
+ // }
33661
+ },
33662
+ "settings": {
33663
+ "defaultLimit": 5,
33664
+ "initMode": "eager"
33665
+ }
33666
+ }
33667
+ `;
33668
+ }
33669
+ async function createDefaultConfigIfMissing(filePath, version3) {
33670
+ try {
33671
+ const file3 = Bun.file(filePath);
33672
+ const exists = await file3.exists();
33673
+ if (exists) {
33674
+ return false;
33675
+ }
33676
+ const dir = dirname(filePath);
33677
+ await mkdir(dir, { recursive: true });
33678
+ const content = generateDefaultConfig(version3);
33679
+ await writeFile(filePath, content, "utf-8");
33680
+ return true;
33681
+ } catch {
33682
+ return false;
33683
+ }
33684
+ }
33639
33685
  function interpolateEnvVars(obj) {
33640
33686
  if (typeof obj === "string") {
33641
33687
  return obj.replace(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, varName) => {
@@ -38343,7 +38389,10 @@ class MCPManager extends EventEmitter {
38343
38389
  } catch (error92) {
38344
38390
  lastError = error92 instanceof Error ? error92 : new Error(String(error92));
38345
38391
  if (attempt < maxAttempts) {
38346
- await sleep(this.connectionConfig.retryDelay);
38392
+ const baseDelay = this.connectionConfig.retryDelay;
38393
+ const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
38394
+ const delayMs = Math.min(exponentialDelay, 30000);
38395
+ await sleep(delayMs);
38347
38396
  }
38348
38397
  }
38349
38398
  }
@@ -38379,7 +38428,7 @@ class MCPManager extends EventEmitter {
38379
38428
  });
38380
38429
  this.clients.set(name, client);
38381
38430
  globalProfiler.recordServerConnect(name, connectTime, catalogTools.length, "connected");
38382
- this.emit("server:connected", name, catalogTools);
38431
+ this.emit("server:connected", name, catalogTools, connectTime);
38383
38432
  this.checkPartialReady();
38384
38433
  }
38385
38434
  checkPartialReady() {
@@ -38693,6 +38742,7 @@ function generateSignature(tool3) {
38693
38742
  return `${tool3.id.name}(${argList})`;
38694
38743
  }
38695
38744
  // src/plugin.ts
38745
+ var PACKAGE_VERSION = "0.8.0";
38696
38746
  var DEFAULT_CONFIG_PATH = `${process.env.HOME}/.config/opencode/toolbox.jsonc`;
38697
38747
  var LOG_FILE_PATH = `${process.env.HOME}/.local/share/opencode/toolbox.log`;
38698
38748
  var LOG_DIR = `${process.env.HOME}/.local/share/opencode`;
@@ -38702,6 +38752,13 @@ var COMMAND_CONTENT = `---
38702
38752
  description: Check toolbox plugin status and server health
38703
38753
  ---
38704
38754
  Run toolbox_status({}) tool and show me the results in a readable format.
38755
+
38756
+ Include:
38757
+ 1. MCP Servers table (name, type, tools, status)
38758
+ 2. Tool distribution chart
38759
+ 3. Toolbox's own tools list (from toolboxTools field)
38760
+ 4. Health status
38761
+
38705
38762
  Highlight any failed servers or issues.
38706
38763
  `;
38707
38764
  function parseToolName(fullName) {
@@ -38833,14 +38890,16 @@ function log(level, message, extra) {
38833
38890
  const extraStr = extra ? ` ${JSON.stringify(extra)}` : "";
38834
38891
  const line = `${timestamp} [${level.toUpperCase()}] ${message}${extraStr}
38835
38892
  `;
38836
- mkdir(LOG_DIR, { recursive: true }).then(() => appendFile(LOG_FILE_PATH, line)).catch(() => {});
38893
+ mkdir2(LOG_DIR, { recursive: true }).then(() => appendFile(LOG_FILE_PATH, line)).catch(() => {});
38837
38894
  }
38838
38895
  function ensureCommandFile() {
38839
38896
  if (isTestEnv)
38840
38897
  return;
38841
- access(COMMAND_FILE_PATH).catch(() => {
38842
- mkdir(COMMAND_DIR, { recursive: true }).then(() => writeFile(COMMAND_FILE_PATH, COMMAND_CONTENT)).then(() => log("info", "Created /toolbox-status command file")).catch(() => {});
38843
- });
38898
+ mkdir2(COMMAND_DIR, { recursive: true }).then(() => readFile(COMMAND_FILE_PATH, "utf-8").catch(() => "")).then((existing) => {
38899
+ if (existing !== COMMAND_CONTENT) {
38900
+ return writeFile2(COMMAND_FILE_PATH, COMMAND_CONTENT).then(() => log("info", existing ? "Updated /toolbox-status command file" : "Created /toolbox-status command file"));
38901
+ }
38902
+ }).catch(() => {});
38844
38903
  }
38845
38904
  function generateSystemPrompt(configuredServers) {
38846
38905
  const registry3 = configuredServers.length > 0 ? configuredServers.map((s) => `${s}_*`).join(`
@@ -38897,9 +38956,19 @@ var ToolboxPlugin = async (ctx) => {
38897
38956
  const pluginLoadStart = performance.now();
38898
38957
  const { client } = ctx;
38899
38958
  const configPath = process.env.OPENCODE_TOOLBOX_CONFIG || DEFAULT_CONFIG_PATH;
38959
+ if (!isTestEnv) {
38960
+ const created = await createDefaultConfigIfMissing(configPath, PACKAGE_VERSION);
38961
+ if (created) {
38962
+ log("info", `Created default config file at ${configPath}`);
38963
+ }
38964
+ }
38900
38965
  const configResult = await loadConfig(configPath);
38901
38966
  if (!configResult.success) {
38902
- const errorMsg = `Failed to load config from ${configPath}: ${configResult.error.issues.map((i) => i.message).join(", ")}`;
38967
+ const formattedErrors = configResult.error.issues.map((issue3) => {
38968
+ const path = issue3.path.length > 0 ? `at "${issue3.path.join(".")}"` : "";
38969
+ return `${issue3.message} ${path}`.trim();
38970
+ }).join("; ");
38971
+ const errorMsg = `Failed to load config from ${configPath}: ${formattedErrors}`;
38903
38972
  log("error", errorMsg);
38904
38973
  return {};
38905
38974
  }
@@ -38927,12 +38996,12 @@ var ToolboxPlugin = async (ctx) => {
38927
38996
  initMode,
38928
38997
  loadDurationMs: Math.round(pluginLoadDuration * 100) / 100
38929
38998
  });
38930
- mcpManager.on("server:connected", (serverName, tools) => {
38999
+ mcpManager.on("server:connected", (serverName, tools, connectTime) => {
38931
39000
  const startTime = performance.now();
38932
39001
  bm25Index.addToolsBatch(tools);
38933
39002
  const indexTime = performance.now() - startTime;
38934
39003
  globalProfiler.recordIncrementalUpdate(tools.length);
38935
- log("info", `Server ${serverName} connected, indexed ${tools.length} tools in ${indexTime.toFixed(2)}ms`);
39004
+ log("info", `${serverName} - connection time: ${connectTime.toFixed(2)}ms, indexed ${tools.length} tools in ${indexTime.toFixed(2)}ms`);
38936
39005
  });
38937
39006
  mcpManager.on("server:error", (serverName, error92) => {
38938
39007
  log("warn", `Server ${serverName} failed: ${error92}`);
@@ -39103,9 +39172,34 @@ var ToolboxPlugin = async (ctx) => {
39103
39172
  error: errorMsg,
39104
39173
  durationMs: duration5
39105
39174
  });
39175
+ const server = mcpManager.getServer(parsed.serverName);
39176
+ const configuredServer = config3.mcp[parsed.serverName];
39177
+ const serverInfo = server ? {
39178
+ name: server.name,
39179
+ status: server.status,
39180
+ type: server.config.type,
39181
+ error: server.error || null,
39182
+ command: server.config.type === "local" ? server.config.command || null : undefined,
39183
+ commandString: server.config.type === "local" && server.config.command ? server.config.command.join(" ") : undefined,
39184
+ url: server.config.type === "remote" ? server.config.url || null : undefined
39185
+ } : configuredServer ? {
39186
+ name: parsed.serverName,
39187
+ status: "unknown",
39188
+ type: configuredServer.type,
39189
+ error: null,
39190
+ command: configuredServer.type === "local" ? configuredServer.command || null : undefined,
39191
+ commandString: configuredServer.type === "local" && configuredServer.command ? configuredServer.command.join(" ") : undefined,
39192
+ url: configuredServer.type === "remote" ? configuredServer.url || null : undefined
39193
+ } : {
39194
+ name: parsed.serverName,
39195
+ status: "unknown",
39196
+ type: "unknown",
39197
+ error: null
39198
+ };
39106
39199
  return JSON.stringify({
39107
39200
  success: false,
39108
- error: errorMsg
39201
+ error: errorMsg,
39202
+ server: serverInfo
39109
39203
  });
39110
39204
  }
39111
39205
  }
@@ -39155,6 +39249,9 @@ var ToolboxPlugin = async (ctx) => {
39155
39249
  type: server.config.type,
39156
39250
  toolCount: server.tools.length,
39157
39251
  error: server.error || null,
39252
+ command: server.config.type === "local" ? server.config.command || null : undefined,
39253
+ commandString: server.config.type === "local" && server.config.command ? server.config.command.join(" ") : undefined,
39254
+ url: server.config.type === "remote" ? server.config.url || null : undefined,
39158
39255
  healthy: server.status === "connected"
39159
39256
  }))
39160
39257
  },
@@ -39163,6 +39260,14 @@ var ToolboxPlugin = async (ctx) => {
39163
39260
  indexed: bm25Index.size,
39164
39261
  serversWithTools: servers.filter((s) => s.tools.length > 0).length
39165
39262
  },
39263
+ toolboxTools: [
39264
+ "toolbox_search_bm25",
39265
+ "toolbox_search_regex",
39266
+ "toolbox_execute",
39267
+ "toolbox_status",
39268
+ "toolbox_perf",
39269
+ "toolbox_test"
39270
+ ],
39166
39271
  health: {
39167
39272
  status: failedServers.length === 0 && servers.length > 0 ? "healthy" : failedServers.length > 0 ? "degraded" : "unknown",
39168
39273
  message: servers.length === 0 ? "No servers configured" : failedServers.length === 0 ? "All servers connected" : `${failedServers.length} server(s) failed to connect`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-toolbox",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Tool Search Tool Plugin for OpenCode - search and execute tools from MCP servers on-demand",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -24,7 +24,8 @@
24
24
  },
25
25
  "files": [
26
26
  "dist",
27
- "README.md"
27
+ "README.md",
28
+ "toolbox.schema.json"
28
29
  ],
29
30
  "keywords": [
30
31
  "opencode",
@@ -0,0 +1,125 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://unpkg.com/opencode-toolbox@latest/toolbox.schema.json",
4
+ "title": "OpenCode Toolbox Configuration",
5
+ "description": "Configuration schema for opencode-toolbox plugin",
6
+ "type": "object",
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string",
10
+ "description": "JSON Schema reference for editor support"
11
+ },
12
+ "mcp": {
13
+ "type": "object",
14
+ "description": "MCP servers to connect to",
15
+ "additionalProperties": {
16
+ "oneOf": [
17
+ {
18
+ "type": "object",
19
+ "description": "Local MCP server (stdio)",
20
+ "properties": {
21
+ "type": {
22
+ "type": "string",
23
+ "const": "local",
24
+ "description": "Server type: local process via stdio"
25
+ },
26
+ "command": {
27
+ "type": "array",
28
+ "items": { "type": "string" },
29
+ "minItems": 1,
30
+ "description": "Command and arguments to spawn the MCP server"
31
+ },
32
+ "environment": {
33
+ "type": "object",
34
+ "additionalProperties": { "type": "string" },
35
+ "description": "Environment variables for the process. Use {env:VAR_NAME} for interpolation."
36
+ }
37
+ },
38
+ "required": ["type", "command"],
39
+ "additionalProperties": false
40
+ },
41
+ {
42
+ "type": "object",
43
+ "description": "Remote MCP server (HTTP/SSE)",
44
+ "properties": {
45
+ "type": {
46
+ "type": "string",
47
+ "const": "remote",
48
+ "description": "Server type: remote HTTP/SSE endpoint"
49
+ },
50
+ "url": {
51
+ "type": "string",
52
+ "format": "uri",
53
+ "description": "Remote MCP endpoint URL"
54
+ },
55
+ "headers": {
56
+ "type": "object",
57
+ "additionalProperties": { "type": "string" },
58
+ "description": "HTTP headers for authentication. Use {env:VAR_NAME} for interpolation."
59
+ }
60
+ },
61
+ "required": ["type", "url"],
62
+ "additionalProperties": false
63
+ }
64
+ ]
65
+ }
66
+ },
67
+ "settings": {
68
+ "type": "object",
69
+ "description": "Plugin settings",
70
+ "properties": {
71
+ "defaultLimit": {
72
+ "type": "integer",
73
+ "minimum": 1,
74
+ "maximum": 20,
75
+ "default": 5,
76
+ "description": "Default number of search results to return"
77
+ },
78
+ "initMode": {
79
+ "type": "string",
80
+ "enum": ["eager", "lazy"],
81
+ "default": "eager",
82
+ "description": "Initialization mode: 'eager' connects on plugin load, 'lazy' connects on first use"
83
+ },
84
+ "connection": {
85
+ "type": "object",
86
+ "description": "Connection settings for MCP servers",
87
+ "properties": {
88
+ "connectTimeout": {
89
+ "type": "integer",
90
+ "minimum": 100,
91
+ "maximum": 60000,
92
+ "default": 5000,
93
+ "description": "Connection timeout in milliseconds"
94
+ },
95
+ "requestTimeout": {
96
+ "type": "integer",
97
+ "minimum": 100,
98
+ "maximum": 300000,
99
+ "default": 30000,
100
+ "description": "Request timeout in milliseconds"
101
+ },
102
+ "retryAttempts": {
103
+ "type": "integer",
104
+ "minimum": 0,
105
+ "maximum": 10,
106
+ "default": 2,
107
+ "description": "Number of retry attempts on connection failure"
108
+ },
109
+ "retryDelay": {
110
+ "type": "integer",
111
+ "minimum": 0,
112
+ "maximum": 30000,
113
+ "default": 1000,
114
+ "description": "Delay between retries in milliseconds"
115
+ }
116
+ },
117
+ "additionalProperties": false
118
+ }
119
+ },
120
+ "additionalProperties": false
121
+ }
122
+ },
123
+ "required": ["mcp"],
124
+ "additionalProperties": false
125
+ }