feat(mcp): reconnect failed MCP servers in managed OpenCode

OpenCode marks an MCP server failed when it does not come up at startup
or when a live connection drops, and never retries. A new managed plugin
reconnects those servers with a per-server backoff (1s doubling to a 30s
cap), wakes early on mcp.tools.changed, and stops on dispose. Only the
failed state is retried; disabled and auth states stay untouched.

The OPENCODE_CONFIG_CONTENT merge that agent-tool and system-prompt each
carried is now one shared helper so the three managed plugins compose.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
This commit is contained in:
Bohdan Triapitsyn
2026-09-04 22:56:27 +03:00
parent caac24ac3d
commit ce5c0c068e
8 changed files with 375 additions and 45 deletions
+2 -19
View File
@@ -1,5 +1,5 @@
import { parse as parseJsonc } from 'jsonc-parser';
import { pathToFileURL } from 'node:url';
import { appendManagedPlugin } from '../opencode/managed-plugin-config.js';
import {
OPENCHAMBER_AGENT_TOOL_ACTION_DEFINITIONS,
OPENCHAMBER_AGENT_TOOL_ACTIONS,
@@ -252,23 +252,6 @@ ${entries.join('')} },
`;
};
const mergePluginConfig = (rawConfig, pluginUrl) => {
const errors = [];
const parsed = asNonEmptyString(rawConfig) ? parseJsonc(rawConfig, errors, { allowTrailingComma: true }) : {};
if (errors.length > 0 || !parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error('OPENCODE_CONFIG_CONTENT must contain a valid JSON object before OpenChamber can inject its managed tool');
}
if (parsed.plugin !== undefined && !Array.isArray(parsed.plugin)) {
throw new Error('OPENCODE_CONFIG_CONTENT plugin must be an array before OpenChamber can inject its managed tool');
}
const configured = Array.isArray(parsed.plugin) ? parsed.plugin : [];
parsed.plugin = [
...configured.filter((value) => value !== pluginUrl && (!Array.isArray(value) || value[0] !== pluginUrl)),
pluginUrl,
];
return JSON.stringify(parsed);
};
export const createAgentToolRuntime = (dependencies) => {
const {
crypto,
@@ -296,7 +279,7 @@ export const createAgentToolRuntime = (dependencies) => {
activeToken = crypto.randomBytes(32).toString('base64url');
const pluginUrl = pathToFileURL(pluginPath).href;
return {
OPENCODE_CONFIG_CONTENT: mergePluginConfig(env.OPENCODE_CONFIG_CONTENT, pluginUrl),
OPENCODE_CONFIG_CONTENT: appendManagedPlugin(env.OPENCODE_CONFIG_CONTENT, pluginUrl, 'managed tool'),
OPENCHAMBER_AGENT_TOOL_URL: `http://127.0.0.1:${port}/api/openchamber/agent-tool`,
OPENCHAMBER_AGENT_TOOL_TOKEN: activeToken,
};