opencode-toolbox 0.4.0 → 0.5.1

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.
Files changed (3) hide show
  1. package/README.md +153 -7
  2. package/dist/index.js +166 -3
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -63,7 +63,7 @@ Create `~/.config/opencode/toolbox.jsonc`:
63
63
 
64
64
  ## Usage
65
65
 
66
- The plugin exposes three tools:
66
+ The plugin exposes four tools:
67
67
 
68
68
  ### toolbox_search_bm25
69
69
 
@@ -135,6 +135,86 @@ Toolbox: { "datetime": "2026-01-07T02:15:00+09:00", "timezone": "Asia/Tokyo" }
135
135
  LLM: "The current time in Tokyo is 2:15 AM on January 7, 2026."
136
136
  ```
137
137
 
138
+ ### toolbox_status
139
+
140
+ Get toolbox status including plugin health, MCP server connections, and tool counts:
141
+
142
+ ```
143
+ toolbox_status({})
144
+ ```
145
+
146
+ Returns a comprehensive status object:
147
+
148
+ ```json
149
+ {
150
+ "plugin": {
151
+ "initialized": true,
152
+ "configPath": "/Users/username/.config/opencode/toolbox.jsonc",
153
+ "uptime": 123.45,
154
+ "searches": 23,
155
+ "executions": 15,
156
+ "successRate": "93%"
157
+ },
158
+ "servers": {
159
+ "total": 3,
160
+ "connected": 2,
161
+ "failed": 1,
162
+ "connecting": 0,
163
+ "connectionRatio": "2/3",
164
+ "details": [
165
+ {
166
+ "name": "time",
167
+ "status": "connected",
168
+ "type": "local",
169
+ "toolCount": 5,
170
+ "error": null,
171
+ "healthy": true
172
+ },
173
+ {
174
+ "name": "github",
175
+ "status": "connected",
176
+ "type": "local",
177
+ "toolCount": 12,
178
+ "error": null,
179
+ "healthy": true
180
+ },
181
+ {
182
+ "name": "weather",
183
+ "status": "error",
184
+ "type": "remote",
185
+ "toolCount": 0,
186
+ "error": "Failed to connect: timeout",
187
+ "healthy": false
188
+ }
189
+ ]
190
+ },
191
+ "tools": {
192
+ "total": 17,
193
+ "available": 17,
194
+ "serversWithTools": 2
195
+ },
196
+ "health": {
197
+ "status": "degraded",
198
+ "message": "1 server(s) failed to connect"
199
+ }
200
+ }
201
+ ```
202
+
203
+ **Health Status:**
204
+ - `healthy`: All servers connected successfully
205
+ - `degraded`: Some servers failed to connect (check `servers.failed`)
206
+ - `unknown`: No servers configured or initialization failed
207
+
208
+ ### /toolbox-status Slash Command
209
+
210
+ The plugin automatically creates a `/toolbox-status` slash command on first launch:
211
+
212
+ ```
213
+ ~/.config/opencode/command/toolbox-status.md
214
+ ```
215
+
216
+ Use it in OpenCode by typing `/toolbox-status` to get a formatted status report.
217
+
138
218
  ## Search Modes
139
219
 
140
220
  ### BM25 (Natural Language)
@@ -172,13 +252,69 @@ bun test --coverage # Run with coverage
172
252
  bun run build
173
253
  ```
174
254
 
255
+ ## Observability
256
+
257
+ The toolbox plugin provides built-in logging and status monitoring to help you understand what's happening.
258
+
259
+ ### Logging
260
+
261
+ All plugin operations are logged **silently** to a dedicated log file (no screen output):
262
+
263
+ ```
264
+ ~/.local/share/opencode/toolbox.log
265
+ ```
266
+
267
+ Log entries include:
268
+ - Plugin initialization status
269
+ - MCP server connection status (connected/error)
270
+ - Tool search operations (BM25/regex queries + result counts)
271
+ - Tool execution results (success/failure with duration)
272
+ - Errors with details
273
+
274
+ **View logs:**
275
+ ```bash
276
+ # Watch logs in real-time
277
+ tail -f ~/.local/share/opencode/toolbox.log
278
+
279
+ # Check for errors only
280
+ grep "ERROR" ~/.local/share/opencode/toolbox.log
281
+
282
+ # Check for warnings
283
+ grep "WARN" ~/.local/share/opencode/toolbox.log
284
+ ```
285
+
286
+ **Log format:**
287
+ ```
288
+ 2026-01-08T12:34:56.789Z [INFO] Toolbox plugin loaded successfully {"configPath":"...","serverCount":6}
289
+ 2026-01-08T12:34:57.123Z [INFO] Initialization complete: 5/6 servers connected, 42 tools indexed
290
+ 2026-01-08T12:34:57.124Z [WARN] 1 server(s) failed to connect: weather
291
+ 2026-01-08T12:35:00.456Z [INFO] BM25 search completed: "web search" -> 3 results
292
+ ```
293
+
294
+ ### Status Tool
295
+
296
+ Use the `toolbox_status` command to check plugin health at any time:
297
+
298
+ ```
299
+ toolbox_status({})
300
+ ```
301
+
302
+ This shows:
303
+ - **Plugin Status**: Initialization, config path, uptime, search/execution counts
304
+ - **Server Status**: Connection ratio (e.g., "2/3"), details per server
305
+ - **Tools**: Total available tools, servers with tools
306
+ - **Health**: Overall health status (healthy/degraded/unknown)
307
+
308
+ **Connection Ratio**: Shows `success/total` for servers. If `success < total`, it indicates failed connections.
309
+
175
310
  ## Troubleshooting
176
311
 
177
312
  ### Plugin not loading
178
313
 
179
- 1. Check OpenCode logs for plugin errors
180
- 2. Verify `opencode-toolbox` is in the `plugin` array in `opencode.jsonc`
181
- 3. Ensure `toolbox.jsonc` exists and is valid JSON
314
+ 1. Run `toolbox_status({})` to check initialization status
315
+ 2. Check OpenCode logs at `~/.local/share/opencode/log/` for plugin errors
316
+ 3. Verify `opencode-toolbox` is in the `plugin` array in `opencode.jsonc`
317
+ 4. Ensure `toolbox.jsonc` exists and is valid JSON
182
318
 
183
319
  ### Search finds no tools
184
320
 
@@ -186,11 +322,21 @@ bun run build
186
322
  2. Check tool descriptions for relevant keywords
187
323
  3. Try broader search terms or regex patterns
188
324
 
325
+ ### MCP servers not connecting
326
+
327
+ 1. Run `toolbox_status({})` to see which servers failed
328
+ 2. Check logs for specific error messages from failed servers
329
+ 3. Verify server command works standalone: `npx -y @anthropic/mcp-time`
330
+ 4. For remote servers, verify URL is accessible
331
+ 5. Check environment variables are set correctly
332
+
189
333
  ### Execute fails
190
334
 
191
- 1. Verify the tool name format: `serverName_toolName`
192
- 2. Check `arguments` is valid JSON
193
- 3. Ensure underlying MCP server is running
335
+ 1. Run `toolbox_status({})` to check server health
336
+ 2. Verify tool name format: `serverName_toolName`
337
+ 3. Check `arguments` is valid JSON
338
+ 4. Ensure underlying MCP server is running and connected
339
+ 5. Check logs for detailed error messages
194
340
 
195
341
  ## License
196
342
 
package/dist/index.js CHANGED
@@ -19266,6 +19266,9 @@ function tool(input) {
19266
19266
  return input;
19267
19267
  }
19268
19268
  tool.schema = exports_external;
19269
+ // src/plugin.ts
19270
+ import { appendFile, mkdir, writeFile, access } from "fs/promises";
19271
+
19269
19272
  // node_modules/zod/v4/classic/external.js
19270
19273
  var exports_external2 = {};
19271
19274
  __export(exports_external2, {
@@ -37910,6 +37913,16 @@ function generateSignature(tool3) {
37910
37913
  }
37911
37914
  // src/plugin.ts
37912
37915
  var DEFAULT_CONFIG_PATH = `${process.env.HOME}/.config/opencode/toolbox.jsonc`;
37916
+ var LOG_FILE_PATH = `${process.env.HOME}/.local/share/opencode/toolbox.log`;
37917
+ var LOG_DIR = `${process.env.HOME}/.local/share/opencode`;
37918
+ var COMMAND_DIR = `${process.env.HOME}/.config/opencode/command`;
37919
+ var COMMAND_FILE_PATH = `${COMMAND_DIR}/toolbox-status.md`;
37920
+ var COMMAND_CONTENT = `---
37921
+ description: Check toolbox plugin status and server health
37922
+ ---
37923
+ Run toolbox_status({}) tool and show me the results in a readable format.
37924
+ Highlight any failed servers or issues.
37925
+ `;
37913
37926
  function parseToolName(fullName) {
37914
37927
  const underscoreIndex = fullName.indexOf("_");
37915
37928
  if (underscoreIndex === -1) {
@@ -37948,12 +37961,29 @@ Returns tools with schemas. Use toolbox_execute() to run them.`;
37948
37961
  var EXECUTE_DESC = `Execute a tool discovered via toolbox_search_bm25 or toolbox_search_regex.
37949
37962
 
37950
37963
  Pass arguments as JSON string matching the tool's schema.`;
37964
+ var STATUS_DESC = `Get toolbox status including plugin initialization, MCP server connections, and tool counts.
37965
+
37966
+ Shows success/total metrics to highlight failures. Use to check if toolbox is working correctly.`;
37967
+ function log(level, message, extra) {
37968
+ const timestamp = new Date().toISOString();
37969
+ const extraStr = extra ? ` ${JSON.stringify(extra)}` : "";
37970
+ const line = `${timestamp} [${level.toUpperCase()}] ${message}${extraStr}
37971
+ `;
37972
+ mkdir(LOG_DIR, { recursive: true }).then(() => appendFile(LOG_FILE_PATH, line)).catch(() => {});
37973
+ }
37974
+ function ensureCommandFile() {
37975
+ access(COMMAND_FILE_PATH).catch(() => {
37976
+ mkdir(COMMAND_DIR, { recursive: true }).then(() => writeFile(COMMAND_FILE_PATH, COMMAND_CONTENT)).then(() => log("info", "Created /toolbox-status command file")).catch(() => {});
37977
+ });
37978
+ }
37951
37979
  var SYSTEM_PROMPT_BASE = `# Extended Toolbox
37952
37980
 
37953
37981
  You have access to an extended toolbox with additional capabilities (web search, time utilities, code search, etc.).
37954
37982
 
37955
37983
  ## Rule
37956
37984
  ALWAYS search before saying "I cannot do that" or "I don't have access to."
37985
+ DO NOT try to execute a tool without having the tool's exact tool schema. If you don't have the tool's schema
37986
+ then run toolbox_search_* to get them.
37957
37987
 
37958
37988
  ## Workflow
37959
37989
  1. Search: toolbox_search_bm25({ text: "what you need" }) or toolbox_search_regex({ pattern: "prefix_.*" })
@@ -37981,26 +38011,60 @@ ${JSON.stringify(toolboxSchema, null, 2)}
37981
38011
  \`\`\``;
37982
38012
  }
37983
38013
  var ToolboxPlugin = async (ctx) => {
38014
+ const { client } = ctx;
37984
38015
  const configPath = process.env.OPENCODE_TOOLBOX_CONFIG || DEFAULT_CONFIG_PATH;
37985
38016
  const configResult = await loadConfig(configPath);
37986
38017
  if (!configResult.success) {
37987
- console.error("[Toolbox] Failed to load config:", configResult.error.issues);
38018
+ const errorMsg = `Failed to load config from ${configPath}: ${configResult.error.issues.map((i) => i.message).join(", ")}`;
38019
+ log("error", errorMsg);
37988
38020
  return {};
37989
38021
  }
37990
38022
  const config3 = configResult.data;
37991
38023
  const mcpManager = new MCPManager;
37992
38024
  const bm25Index = new BM25Index;
37993
38025
  let initialized = false;
38026
+ let searchCount = 0;
38027
+ let executionCount = 0;
38028
+ let executionSuccessCount = 0;
38029
+ ensureCommandFile();
38030
+ const serverNames = Object.keys(config3.mcp);
38031
+ log("info", `Toolbox plugin loaded successfully`, {
38032
+ configPath,
38033
+ serverCount: serverNames.length,
38034
+ servers: serverNames
38035
+ });
37994
38036
  async function ensureInitialized() {
37995
38037
  if (initialized)
37996
38038
  return;
37997
38039
  try {
38040
+ log("info", "Initializing MCP servers...");
37998
38041
  await mcpManager.initialize(config3.mcp);
37999
38042
  const allTools = mcpManager.getAllCatalogTools();
38000
38043
  bm25Index.indexTools(allTools);
38001
38044
  initialized = true;
38045
+ const servers = mcpManager.getAllServers();
38046
+ const connectedServers = servers.filter((s) => s.status === "connected");
38047
+ const failedServers = servers.filter((s) => s.status === "error");
38048
+ const initMsg = `Initialization complete: ${connectedServers.length}/${servers.length} servers connected, ${allTools.length} tools indexed`;
38049
+ log("info", initMsg, {
38050
+ totalServers: servers.length,
38051
+ connectedServers: connectedServers.length,
38052
+ failedServers: failedServers.length,
38053
+ totalTools: allTools.length,
38054
+ servers: servers.map((s) => ({
38055
+ name: s.name,
38056
+ status: s.status,
38057
+ toolCount: s.tools.length,
38058
+ error: s.error || null
38059
+ }))
38060
+ });
38061
+ if (failedServers.length > 0) {
38062
+ const warnMsg = `${failedServers.length} server(s) failed to connect: ${failedServers.map((s) => s.name).join(", ")}`;
38063
+ log("warn", warnMsg);
38064
+ }
38002
38065
  } catch (error92) {
38003
- console.error("[Toolbox] Failed to initialize MCP servers:", error92);
38066
+ const errorMsg = `Failed to initialize MCP servers: ${error92 instanceof Error ? error92.message : String(error92)}`;
38067
+ log("error", errorMsg);
38004
38068
  throw error92;
38005
38069
  }
38006
38070
  }
@@ -38021,9 +38085,16 @@ var ToolboxPlugin = async (ctx) => {
38021
38085
  error: `Failed to initialize: ${error92 instanceof Error ? error92.message : String(error92)}`
38022
38086
  });
38023
38087
  }
38088
+ searchCount++;
38024
38089
  const searchLimit = args.limit || config3.settings?.defaultLimit || 5;
38025
38090
  const allTools = mcpManager.getAllCatalogTools();
38026
38091
  const results = bm25Index.search(args.text, searchLimit);
38092
+ log("info", `BM25 search completed: "${args.text}" -> ${results.length} results`, {
38093
+ searchType: "bm25",
38094
+ query: args.text,
38095
+ resultsCount: results.length,
38096
+ limit: searchLimit
38097
+ });
38027
38098
  return formatSearchResults(results, allTools);
38028
38099
  }
38029
38100
  }),
@@ -38042,15 +38113,23 @@ var ToolboxPlugin = async (ctx) => {
38042
38113
  error: `Failed to initialize: ${error92 instanceof Error ? error92.message : String(error92)}`
38043
38114
  });
38044
38115
  }
38116
+ searchCount++;
38045
38117
  const searchLimit = args.limit || config3.settings?.defaultLimit || 5;
38046
38118
  const allTools = mcpManager.getAllCatalogTools();
38047
38119
  const result = searchWithRegex(allTools, args.pattern, searchLimit);
38048
38120
  if ("error" in result) {
38121
+ log("warn", `Regex search failed: "${args.pattern}" -> ${result.error}`);
38049
38122
  return JSON.stringify({
38050
38123
  success: false,
38051
38124
  error: result.error
38052
38125
  });
38053
38126
  }
38127
+ log("info", `Regex search completed: "${args.pattern}" -> ${result.length} results`, {
38128
+ searchType: "regex",
38129
+ pattern: args.pattern,
38130
+ resultsCount: result.length,
38131
+ limit: searchLimit
38132
+ });
38054
38133
  return formatSearchResults(result, allTools);
38055
38134
  }
38056
38135
  }),
@@ -38071,6 +38150,9 @@ var ToolboxPlugin = async (ctx) => {
38071
38150
  }
38072
38151
  const parsed = parseToolName(args.name);
38073
38152
  if (!parsed) {
38153
+ log("warn", `Invalid tool name format: ${args.name}`, {
38154
+ toolName: args.name
38155
+ });
38074
38156
  return JSON.stringify({
38075
38157
  success: false,
38076
38158
  error: `Invalid tool name format: ${args.name}. Expected format: serverName_toolName (e.g., 'time_get_current_time')`
@@ -38081,25 +38163,106 @@ var ToolboxPlugin = async (ctx) => {
38081
38163
  try {
38082
38164
  toolArgs = JSON.parse(args.arguments);
38083
38165
  } catch (error92) {
38166
+ log("warn", `Failed to parse arguments as JSON for ${args.name}`, {
38167
+ toolName: args.name,
38168
+ arguments: args.arguments
38169
+ });
38084
38170
  return JSON.stringify({
38085
38171
  success: false,
38086
38172
  error: `Failed to parse arguments as JSON: ${error92 instanceof Error ? error92.message : String(error92)}`
38087
38173
  });
38088
38174
  }
38089
38175
  }
38176
+ executionCount++;
38090
38177
  try {
38178
+ const startTime = Date.now();
38091
38179
  const result = await mcpManager.callTool(parsed.serverName, parsed.toolName, toolArgs);
38180
+ const duration5 = Date.now() - startTime;
38181
+ executionSuccessCount++;
38182
+ log("info", `Tool executed successfully: ${args.name}`, {
38183
+ server: parsed.serverName,
38184
+ tool: parsed.toolName,
38185
+ durationMs: duration5
38186
+ });
38092
38187
  return JSON.stringify({
38093
38188
  success: true,
38094
38189
  result
38095
38190
  });
38096
38191
  } catch (error92) {
38192
+ const errorMsg = `Tool execution failed: ${error92 instanceof Error ? error92.message : String(error92)}`;
38193
+ log("error", errorMsg, {
38194
+ server: parsed.serverName,
38195
+ tool: parsed.toolName,
38196
+ error: errorMsg
38197
+ });
38097
38198
  return JSON.stringify({
38098
38199
  success: false,
38099
- error: `Tool execution failed: ${error92 instanceof Error ? error92.message : String(error92)}`
38200
+ error: errorMsg
38100
38201
  });
38101
38202
  }
38102
38203
  }
38204
+ }),
38205
+ toolbox_status: tool({
38206
+ description: STATUS_DESC,
38207
+ args: {},
38208
+ async execute() {
38209
+ if (!initialized) {
38210
+ try {
38211
+ await ensureInitialized();
38212
+ } catch (error92) {
38213
+ return JSON.stringify({
38214
+ status: "error",
38215
+ message: "Failed to initialize toolbox",
38216
+ error: error92 instanceof Error ? error92.message : String(error92)
38217
+ });
38218
+ }
38219
+ }
38220
+ const servers = mcpManager.getAllServers();
38221
+ const connectedServers = servers.filter((s) => s.status === "connected");
38222
+ const failedServers = servers.filter((s) => s.status === "error");
38223
+ const connectingServers = servers.filter((s) => s.status === "connecting");
38224
+ const totalTools = mcpManager.getAllCatalogTools().length;
38225
+ const status = {
38226
+ plugin: {
38227
+ initialized: true,
38228
+ configPath,
38229
+ uptime: process.uptime(),
38230
+ searches: searchCount,
38231
+ executions: executionCount,
38232
+ successRate: executionCount > 0 ? `${Math.round(executionSuccessCount / executionCount * 100)}%` : "N/A"
38233
+ },
38234
+ servers: {
38235
+ total: servers.length,
38236
+ connected: connectedServers.length,
38237
+ failed: failedServers.length,
38238
+ connecting: connectingServers.length,
38239
+ connectionRatio: `${connectedServers.length}/${servers.length}`,
38240
+ details: servers.map((server) => ({
38241
+ name: server.name,
38242
+ status: server.status,
38243
+ type: server.config.type,
38244
+ toolCount: server.tools.length,
38245
+ error: server.error || null,
38246
+ healthy: server.status === "connected"
38247
+ }))
38248
+ },
38249
+ tools: {
38250
+ total: totalTools,
38251
+ available: totalTools,
38252
+ serversWithTools: servers.filter((s) => s.tools.length > 0).length
38253
+ },
38254
+ health: {
38255
+ status: failedServers.length === 0 && servers.length > 0 ? "healthy" : failedServers.length > 0 ? "degraded" : "unknown",
38256
+ message: servers.length === 0 ? "No servers configured" : failedServers.length === 0 ? "All servers connected" : `${failedServers.length} server(s) failed to connect`
38257
+ }
38258
+ };
38259
+ log("info", `Status requested: ${connectedServers.length}/${servers.length} servers connected`, {
38260
+ connectedServers: connectedServers.length,
38261
+ totalServers: servers.length,
38262
+ totalTools
38263
+ });
38264
+ return JSON.stringify(status, null, 2);
38265
+ }
38103
38266
  })
38104
38267
  },
38105
38268
  "experimental.chat.system.transform": async (_input, output) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-toolbox",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
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",