Description
Argus indexes local agent transcripts, but agents can't see any of it. Let AI agents (Claude Code, Codex, Gemini CLI) query Argus — "what did I work on last week", session history, usage/cost, task outcomes — so agents can use the user's own work history as context.
Approach
A local MCP server, served as streamable HTTP on the existing serve app at /mcp — reachable at http://127.0.0.1:4242/mcp for both desktop-app users (via the front-door proxy: no PATH dependency, survives sidecar restarts) and CLI users. No stdio subcommand in v1: desktop users' sidecar binary lives at an unstable bundle path, and "works while Argus isn't running" isn't a real use case when the daemon is the product. Claude Code (claude mcp add --transport http), Codex ([mcp_servers.argus] in config.toml), and Gemini CLI all speak streamable HTTP. The zero-code alternative (document the HTTP API + argus search --json) becomes a short fallback section on the docs page.
Privacy defaults: agentAccess.enabled default ON (UI toggle — MCP adds discoverability, not new exposure, since the read API is already open to local processes; matches the interpret default-on precedent). agentAccess.includeTranscripts default OFF (UI toggle — the genuinely new risk is transcript text flowing into a remote model's context). Both resolve per request so Settings toggles apply live; disabled → /mcp returns 404. The transcript tool stays listed when off, with a description explaining how to enable it.
Six read-only tools, all reusing readers startServer already assembles (zero new store queries):
| Tool |
Reuses |
search_sessions (query/file/since/until/source/project/sort, limit ≤50) |
sessionList reader → store.searchSessions + readSessionAggregates + buildSessionList; strip FTS snippet sentinels like search-ops.ts does |
get_session (+optional task metrics) |
sessionDetail + sessionTaskMetrics readers |
get_session_transcript (paginated; gated by includeTranscripts + retainText) |
sessionInteractions reader (buildSessionInteractions) |
usage_summary (group_by day|model|source|project) |
views.usageDaily/usageByModel/usageBySource/usageByProject |
tool_usage (group_by tool|category|mcp_server|skill) |
views.toolsByTool/toolsByCategory/toolsByMcpServer/skills |
health_summary |
views.health + views.recommendations |
Server instructions teach date semantics (local YYYY-MM-DD; the agent computes "last week" itself) and valid sources (ALL_SOURCES). Tools only — no MCP resources/prompts in v1.
Security: guard /mcp with the existing rejectUnsafeHost (the MCP spec requires Origin validation against DNS rebinding; it tolerates absent Origin, which MCP clients satisfy). Do NOT require the x-argus-app CSRF header (MCP clients can't send it; the endpoint is read-only). No auth token — loopback-only bind stays the boundary, consistent with the rest of the API. Mount among read routes so it survives readOnly mode.
Implementation steps
- Dependencies (root
package.json): @modelcontextprotocol/sdk (import only server/mcp.js + types subpaths — never its node/express transports), @hono/mcp (StreamableHTTPTransport, Bun-supported, takes a Hono Context), zod@^3. Risk to retire early: verify bun run build:compile bundles the SDK, then smoke-test dist/argus serve + a JSON-RPC initialize POST.
- Config (
src/config.ts): new AGENT_ACCESS_SETTINGS registry — enabled (path agentAccess.enabled, env ARGUS_AGENT_ACCESS_ENABLED, default true, toggle UI) and includeTranscripts (path agentAccess.includeTranscripts, env ARGUS_AGENT_ACCESS_INCLUDE_TRANSCRIPTS, default false, toggle UI with text-leaves-machine warning); resolveAgentAccess() modeled on resolveRetainText; add to ALL_SETTINGS.
- Tool registry (
src/api/mcp.ts, new): createArgusMcpServer(deps, { includeTranscripts }) registering the 6 tools (zod schemas; JSON in content text + structuredContent); createMcpHandler(deps) → per-request: resolve gates, 404 if disabled, fresh McpServer + StreamableHTTPTransport (stateless), transport.handleRequest(c). McpDeps = the reader types from serve.ts (export the ones not yet exported).
- Serve wiring (
src/api/serve.ts): AppOptions.mcp?: (c: Context) => Promise<Response> (injected like views, keeps createApp unit-testable); mount app.all("/mcp", ...) among read routes behind rejectUnsafeHost; in startServer, pass createMcpHandler({ views, sessionList, sessionDetail, sessionInteractions, sessionTaskMetrics }). No run.ts/cli.ts changes — it rides the serve leg.
- Settings surface (
src/api/settings.ts): new layout category "Agent access" with the two toggles; the web Settings screen renders from describeSettings(), so no web code changes required (connect-snippet helper copy in web/src/routes/Settings.tsx is a nice-to-have).
- Tests:
test/mcp.test.ts (new) following test/serve.test.ts's fake-reader pattern — drive createApp(null, { mcp: createMcpHandler(fakeDeps) }) with raw JSON-RPC POSTs (initialize, tools/list, tools/call per tool); assert filter pass-through, sentinel stripping, limit clamping, gating (enabled=false → 404; transcripts off → refusal; non-loopback Host → 403). Config resolution tests mirror the resolveRetainText ones.
- Docs:
docs/connect-your-agent.md (new, under "Using Argus" in the VitePress sidebar; contributing voice): per-client connect snippets, what agents can ask, the transcript toggle, HTTP-API fallback. docs/internals/agent-access.md (new, styled like web-app.md): transport decision, tool→reader map, threat model. Update docs/privacy.md and docs/settings-reference.md. Flag: Claude Cowork's sandboxed VM may not reach host loopback — document Claude Code/Codex/Gemini first, verify Cowork separately.
v1 non-goals
No stdio argus mcp subcommand (phase 2: extract reader assembly into a shared factory, ~50-line wrapper); no auth token; no write tools; no MCP resources/prompts; no one-click client-config install from the desktop app; no Hub/org-level access; no per-agent audit log; no transcript redaction.
Phase 2 candidates
stdio subcommand · one-click connect from tray/Settings (writes client configs) · optional bearer token for multi-user machines · MCP prompts ("weekly review", "cost report") · Hub-side org endpoint with real auth. Free bonus: agents querying Argus show up in Argus's own tool analytics as mcp__argus__*.
Verification
bun test (new mcp/config/settings tests + existing suites) and bun run typecheck.
- Compile smoke:
bun run build:compile, run dist/argus serve, POST JSON-RPC initialize + tools/list + a tools/call to http://127.0.0.1:4242/mcp.
- Live end-to-end:
claude mcp add --transport http argus http://127.0.0.1:4242/mcp, ask Claude Code "what did I work on last week?", confirm sensible search_sessions/usage_summary calls; toggle transcripts and enabled in Settings and confirm gates apply without restart.
- Desktop path: confirm
/mcp is reachable through the front-door proxy on 4242.
Description
Argus indexes local agent transcripts, but agents can't see any of it. Let AI agents (Claude Code, Codex, Gemini CLI) query Argus — "what did I work on last week", session history, usage/cost, task outcomes — so agents can use the user's own work history as context.
Approach
A local MCP server, served as streamable HTTP on the existing serve app at
/mcp— reachable athttp://127.0.0.1:4242/mcpfor both desktop-app users (via the front-door proxy: no PATH dependency, survives sidecar restarts) and CLI users. No stdio subcommand in v1: desktop users' sidecar binary lives at an unstable bundle path, and "works while Argus isn't running" isn't a real use case when the daemon is the product. Claude Code (claude mcp add --transport http), Codex ([mcp_servers.argus]in config.toml), and Gemini CLI all speak streamable HTTP. The zero-code alternative (document the HTTP API +argus search --json) becomes a short fallback section on the docs page.Privacy defaults:
agentAccess.enableddefault ON (UI toggle — MCP adds discoverability, not new exposure, since the read API is already open to local processes; matches the interpret default-on precedent).agentAccess.includeTranscriptsdefault OFF (UI toggle — the genuinely new risk is transcript text flowing into a remote model's context). Both resolve per request so Settings toggles apply live; disabled →/mcpreturns 404. The transcript tool stays listed when off, with a description explaining how to enable it.Six read-only tools, all reusing readers
startServeralready assembles (zero new store queries):search_sessions(query/file/since/until/source/project/sort, limit ≤50)sessionListreader →store.searchSessions+readSessionAggregates+buildSessionList; strip FTS snippet sentinels likesearch-ops.tsdoesget_session(+optional task metrics)sessionDetail+sessionTaskMetricsreadersget_session_transcript(paginated; gated by includeTranscripts + retainText)sessionInteractionsreader (buildSessionInteractions)usage_summary(group_by day|model|source|project)views.usageDaily/usageByModel/usageBySource/usageByProjecttool_usage(group_by tool|category|mcp_server|skill)views.toolsByTool/toolsByCategory/toolsByMcpServer/skillshealth_summaryviews.health+views.recommendationsServer
instructionsteach date semantics (local YYYY-MM-DD; the agent computes "last week" itself) and valid sources (ALL_SOURCES). Tools only — no MCP resources/prompts in v1.Security: guard
/mcpwith the existingrejectUnsafeHost(the MCP spec requires Origin validation against DNS rebinding; it tolerates absent Origin, which MCP clients satisfy). Do NOT require thex-argus-appCSRF header (MCP clients can't send it; the endpoint is read-only). No auth token — loopback-only bind stays the boundary, consistent with the rest of the API. Mount among read routes so it survivesreadOnlymode.Implementation steps
package.json):@modelcontextprotocol/sdk(import onlyserver/mcp.js+ types subpaths — never its node/express transports),@hono/mcp(StreamableHTTPTransport, Bun-supported, takes a HonoContext),zod@^3. Risk to retire early: verifybun run build:compilebundles the SDK, then smoke-testdist/argus serve+ a JSON-RPCinitializePOST.src/config.ts): newAGENT_ACCESS_SETTINGSregistry —enabled(pathagentAccess.enabled, envARGUS_AGENT_ACCESS_ENABLED, default true, toggle UI) andincludeTranscripts(pathagentAccess.includeTranscripts, envARGUS_AGENT_ACCESS_INCLUDE_TRANSCRIPTS, default false, toggle UI with text-leaves-machine warning);resolveAgentAccess()modeled onresolveRetainText; add toALL_SETTINGS.src/api/mcp.ts, new):createArgusMcpServer(deps, { includeTranscripts })registering the 6 tools (zod schemas; JSON incontenttext +structuredContent);createMcpHandler(deps)→ per-request: resolve gates, 404 if disabled, freshMcpServer+StreamableHTTPTransport(stateless),transport.handleRequest(c).McpDeps= the reader types from serve.ts (export the ones not yet exported).src/api/serve.ts):AppOptions.mcp?: (c: Context) => Promise<Response>(injected likeviews, keepscreateAppunit-testable); mountapp.all("/mcp", ...)among read routes behindrejectUnsafeHost; instartServer, passcreateMcpHandler({ views, sessionList, sessionDetail, sessionInteractions, sessionTaskMetrics }). Norun.ts/cli.tschanges — it rides the serve leg.src/api/settings.ts): new layout category "Agent access" with the two toggles; the web Settings screen renders fromdescribeSettings(), so no web code changes required (connect-snippet helper copy inweb/src/routes/Settings.tsxis a nice-to-have).test/mcp.test.ts(new) followingtest/serve.test.ts's fake-reader pattern — drivecreateApp(null, { mcp: createMcpHandler(fakeDeps) })with raw JSON-RPC POSTs (initialize,tools/list,tools/callper tool); assert filter pass-through, sentinel stripping, limit clamping, gating (enabled=false→ 404; transcripts off → refusal; non-loopback Host → 403). Config resolution tests mirror theresolveRetainTextones.docs/connect-your-agent.md(new, under "Using Argus" in the VitePress sidebar; contributing voice): per-client connect snippets, what agents can ask, the transcript toggle, HTTP-API fallback.docs/internals/agent-access.md(new, styled likeweb-app.md): transport decision, tool→reader map, threat model. Updatedocs/privacy.mdanddocs/settings-reference.md. Flag: Claude Cowork's sandboxed VM may not reach host loopback — document Claude Code/Codex/Gemini first, verify Cowork separately.v1 non-goals
No stdio
argus mcpsubcommand (phase 2: extract reader assembly into a shared factory, ~50-line wrapper); no auth token; no write tools; no MCP resources/prompts; no one-click client-config install from the desktop app; no Hub/org-level access; no per-agent audit log; no transcript redaction.Phase 2 candidates
stdio subcommand · one-click connect from tray/Settings (writes client configs) · optional bearer token for multi-user machines · MCP prompts ("weekly review", "cost report") · Hub-side org endpoint with real auth. Free bonus: agents querying Argus show up in Argus's own tool analytics as
mcp__argus__*.Verification
bun test(new mcp/config/settings tests + existing suites) andbun run typecheck.bun run build:compile, rundist/argus serve, POST JSON-RPCinitialize+tools/list+ atools/calltohttp://127.0.0.1:4242/mcp.claude mcp add --transport http argus http://127.0.0.1:4242/mcp, ask Claude Code "what did I work on last week?", confirm sensiblesearch_sessions/usage_summarycalls; toggle transcripts and enabled in Settings and confirm gates apply without restart./mcpis reachable through the front-door proxy on 4242.