feat: Add Flaky MCP to headless OpenCode sessions (no-changelog) (#37809)

This commit is contained in:
Declan Carroll 2026-09-04 07:58:32 +00:00 committed by GitHub
parent a9b713be58
commit fcabdcae3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 62 additions and 10 deletions

View File

@ -65,7 +65,7 @@ It runs the turn. It sends the result to the turn's resume URL. It uses no
tunnel, no open port, and no domain.
The worker starts on each container start (`postStartCommand`). It needs three
secrets and uses one optional secret. Add them at
secrets and uses three optional secrets. Add them at
[github.com/settings/codespaces](https://github.com/settings/codespaces), the
same way as `ANTHROPIC_API_KEY`:
@ -73,6 +73,7 @@ same way as `ANTHROPIC_API_KEY`:
- `N8N_DEQUEUE_URL` — the n8n webhook that returns a pending turn.
- `OPENROUTER_API_KEY` — the model provider key for Sol.
- `SLACK_BOT_TOKEN` — optional bot token for progress messages. It needs `chat:write` only.
- `FLAKY_MCP_URL` and `FLAKY_MCP_TOKEN` — optional Flaky MCP connection for OpenCode.
The worker explicitly reloads `/usr/local/lib/codespaces-env.sh` when its tmux
session starts. An existing tmux server can otherwise retain an environment
@ -147,11 +148,12 @@ session rarely needs a cold `pnpm install` or a full `pnpm build`. Both are slow
## Flaky tools (MCP)
Claude sessions get the `flaky` MCP server automatically: Currents
Claude sessions and the headless OpenCode worker get the `flaky` MCP server automatically: Currents
flaky/quarantine data, the `qa_*` BigQuery dataset, Sentry RCA, live Linear,
and repo investigation. Login registers it from the repo-level
`FLAKY_MCP_TOKEN` / `FLAKY_MCP_URL` secrets. Forks have no secrets and skip
it. Tell the agent to call `get_flaky_context` first — it returns the rules
and repo investigation. The worker keeps the token in its environment and puts
only an environment reference in the OpenCode config. Claude login registers
the same server without writing the token to disk. Forks have no secrets and
skip it. Tell the agent to call `get_flaky_context` first — it returns the rules
the tools assume.
## Quality and security skills (Claude plugins)

View File

@ -19,9 +19,24 @@ const INITIAL_POLL_INTERVAL_MS = 3000;
const MAX_POLL_INTERVAL_MS = 30_000;
const SLACK_UPDATE_INTERVAL_MS = 1500;
const SLACK_TEXT_LIMIT = 3900;
const OPENCODE_CONFIG_CONTENT = JSON.stringify({
provider: { openrouter: { options: { apiKey: '{env:OPENROUTER_API_KEY}' } } },
});
export function openCodeConfig(environment) {
const config = {
provider: { openrouter: { options: { apiKey: '{env:OPENROUTER_API_KEY}' } } },
};
if (environment.FLAKY_MCP_URL && environment.FLAKY_MCP_TOKEN) {
config.mcp = {
flaky: {
type: 'remote',
url: environment.FLAKY_MCP_URL,
enabled: true,
oauth: false,
headers: { Authorization: 'Bearer {env:FLAKY_MCP_TOKEN}' },
},
};
}
return config;
}
function posNum(name, fallback) {
const raw = process.env[name];
@ -74,7 +89,7 @@ export function openCodeEnvironment(environment) {
delete childEnvironment.AGENT_WORKER_TOKEN;
delete childEnvironment.N8N_DEQUEUE_URL;
delete childEnvironment.SLACK_BOT_TOKEN;
childEnvironment.OPENCODE_CONFIG_CONTENT = OPENCODE_CONFIG_CONTENT;
childEnvironment.OPENCODE_CONFIG_CONTENT = JSON.stringify(openCodeConfig(childEnvironment));
return childEnvironment;
}

View File

@ -5,7 +5,13 @@ import { PassThrough } from 'node:stream';
import { test } from 'node:test';
import { setTimeout as sleep } from 'node:timers/promises';
import { openCodeEnvironment, pollOnce, runOpenCode, startSlackProgress } from './agent-worker.mjs';
import {
openCodeConfig,
openCodeEnvironment,
pollOnce,
runOpenCode,
startSlackProgress,
} from './agent-worker.mjs';
const turn = {
turnId: 'turn-1',
@ -341,3 +347,32 @@ test('keeps broker credentials out of the OpenCode process', () => {
GITHUB_TOKEN: 'github',
});
});
test('configures Flaky MCP without serializing its token', () => {
const token = 'flaky-secret';
const environment = openCodeEnvironment({
FLAKY_MCP_URL: 'https://example.com/mcp',
FLAKY_MCP_TOKEN: token,
OPENROUTER_API_KEY: 'openrouter',
});
assert.deepEqual(JSON.parse(environment.OPENCODE_CONFIG_CONTENT), {
provider: { openrouter: { options: { apiKey: '{env:OPENROUTER_API_KEY}' } } },
mcp: {
flaky: {
type: 'remote',
url: 'https://example.com/mcp',
enabled: true,
oauth: false,
headers: { Authorization: 'Bearer {env:FLAKY_MCP_TOKEN}' },
},
},
});
assert.equal(environment.FLAKY_MCP_TOKEN, token);
assert.equal(environment.OPENCODE_CONFIG_CONTENT.includes(token), false);
});
test('omits Flaky MCP when either setting is missing', () => {
assert.equal(openCodeConfig({ FLAKY_MCP_URL: 'https://example.com/mcp' }).mcp, undefined);
assert.equal(openCodeConfig({ FLAKY_MCP_TOKEN: 'flaky-secret' }).mcp, undefined);
});