Skip to content

feat: CoDA MCP server for Genie Code integration#64

Open
datasciencemonkey wants to merge 3 commits into
mainfrom
feat/coda-mcp-server
Open

feat: CoDA MCP server for Genie Code integration#64
datasciencemonkey wants to merge 3 commits into
mainfrom
feat/coda-mcp-server

Conversation

@datasciencemonkey
Copy link
Copy Markdown
Collaborator

@datasciencemonkey datasciencemonkey commented May 25, 2026

Summary

  • Mounts an MCP server at /mcp so Databricks Genie Code (and other MCP clients like Claude Desktop, Cursor) can delegate coding tasks to Hermes Agent over the MCP protocol.
  • Exposes three high-level tools following the v2 background-execution pattern: coda_run, coda_inbox, coda_get_result. Session and task state persists to ~/.coda/sessions/.
  • Switches the production entrypoint from gunicorn → uvicorn so we can serve the MCP ASGI app and the existing Flask UI side-by-side (Flask mounted via WSGIMiddleware). WebSocket falls back to HTTP polling under uvicorn — the existing Web Worker poller already handles this.
  • Adds a stdio MCP bridge (tools/coda-bridge.py) so Claude Code's OAuth flow can talk to CoDA.
  • Reshuffles setup_*.pysetup/ and install_*.shscripts/ so the repo root stays uncluttered.

Architecture highlights

  • Native MCP SDK transport (FastMCP.streamable_http_app()) — required by Genie Code's Custom MCP server picker. Custom JSON-RPC handlers don't work.
  • stateless_http=True, json_response=True. DNS-rebinding protection disabled (the Databricks Apps proxy handles auth; workspace origin allowed via CORSMiddleware).
  • CSP/security headers skipped on the /mcp path (CSP interfered with Genie Code's transport).
  • Hermes is the only agent invoked at the entry point; it routes to sub-agents internally per the existing config.
  • The CODA-TASK prompt envelope explicitly forbids destructive operations (DROP/DELETE/TRUNCATE, CLI deletes, permission changes) at the prompt level, per the CoDA Constitution.

Deployment notes

  • App name must start with mcp- to appear in the Genie Code Custom MCP server picker.
  • Tested as mcp-test-coda on workspace fevm-serverless-9cefok (profile 9cefok).
  • See docs/plans/2026-05-01-coda-mcp-server.md for the full design rationale and docs/mcp-v2-background-execution.md for the v2 background flow diagram.

Provenance

This work was originally developed on a personal fork at datasciencemonkey/coding-agents-databricks-apps#156 across 40 commits, last working tip 1ce86bf. Squashed for clean review against current labs main. Full 40-commit history retained locally on tag coda-mcp-backup-2026-05-25.

Two conflicts surfaced during the squash and were resolved as follows:

  • README.md MLflow section: kept main's Claude+Codex unified switch (newer than coda-mcp's Claude-only state).
  • setup/setup_claude.py: combined main's enterprise installer URL handling (security feature) with coda-mcp's SKIP_CLAUDE_INSTALL test escape hatch.

Test plan

  • make test passes locally (478 passed, 1 skipped, 0 failed in ~21s)
  • Smoke: uvicorn coda_mcp.mcp_asgi:app boots without traceback
  • Smoke: from coda_mcp import mcp_server, mcp_asgi, task_manager succeeds
  • Manual: connect Genie Code to /mcp, run one task end-to-end via coda_run
  • Manual: confirm Flask UI still serves at / (terminal works under uvicorn's polling fallback)
  • Confirm CI passes (lint + unit tests + security suite)

Mounts an MCP server at `/mcp` so Databricks Genie Code (and other MCP
clients like Claude Desktop, Cursor) can delegate coding tasks to the
existing Hermes Agent infrastructure. Exposes three high-level tools
following the v2 background-execution pattern:

  - coda_run        — submit a coding task, returns task_id immediately
  - coda_inbox      — poll all task statuses (24h window)
  - coda_get_result — fetch structured output of a completed task

Plus internal helpers (`coda_create_session`, `coda_get_status`,
`coda_close_session`). Sessions and task state are persisted to disk
under `~/.coda/sessions/` so tasks survive worker restarts.

Architecture
------------
- Native MCP SDK transport (`FastMCP.streamable_http_app()`) — required
  by Genie Code's Custom MCP server picker (custom JSON-RPC handlers
  don't work).
- `stateless_http=True`, `json_response=True`. DNS-rebinding protection
  disabled (proxy handles auth, workspace origin allowed via CORS
  middleware).
- Switches the production entrypoint from gunicorn → uvicorn so we can
  serve both the MCP ASGI app and the existing Flask UI side-by-side
  (Flask mounted via WSGIMiddleware). WebSocket falls back to HTTP
  polling under uvicorn — acceptable per the design doc; the Web Worker
  poller is already in place.
- Skips CSP/security headers on the `/mcp` path (CSP interfered with
  Genie Code's transport).
- Hermes is always the agent invoked; it routes to sub-agents
  internally.
- Adds a stdio MCP bridge (`tools/coda-bridge.py`) for Claude Code's
  OAuth-based auth flow.

Repository reshuffles
---------------------
- New `coda_mcp/` package: `mcp_server`, `mcp_endpoint`, `mcp_asgi`,
  `task_manager`.
- `setup_*.py` moved from repo root to `setup/`.
- `install_*.sh` moved from repo root to `scripts/`.
- Tests: new coverage for the MCP server, integration flow, task
  manager, content filter proxy, sync_to_workspace, _run_step.
- Docs: `docs/mcp-client-setup.md`, `docs/mcp-v2-background-execution.md`,
  and the full implementation plan at
  `docs/plans/2026-05-01-coda-mcp-server.md`.

Safety guardrails
-----------------
The CODA-TASK prompt envelope explicitly forbids destructive operations
(DROP/DELETE/TRUNCATE, CLI deletes, permission changes) at the prompt
level, in line with the CoDA Constitution.

Tested as `mcp-test-coda` on workspace `fevm-serverless-9cefok`
(profile `9cefok`). App name must start with `mcp-` to appear in the
Genie Code Custom MCP server picker.

Provenance
----------
Squashed from 40 commits originally on
`datasciencemonkey/coding-agents-databricks-apps#156`, last working
tip `1ce86bf`. Full commit-by-commit history preserved locally on the
tag `coda-mcp-backup-2026-05-25`.

Conflict resolutions during the squash:
  - README.md MLflow section: kept main's Claude+Codex unified switch
    (newer than coda-mcp's Claude-only state).
  - setup/setup_claude.py: combined main's enterprise installer URL
    handling with coda-mcp's `SKIP_CLAUDE_INSTALL` test escape hatch.
Surfaces a doc audit pass against the squash:

- README: replace the gunicorn+Flask architecture diagram with the
  actual uvicorn ASGI stack (socketio.ASGIApp → /mcp + WSGI(Flask)).
  Update the startup-flow narrative, the "Server" config section
  (was "Gunicorn"), the project-structure annotations for app.yaml
  and gunicorn.conf.py (legacy, retained for WSGI-only dev), and the
  Technologies list.
- app.yaml: prepend a comment block explaining why the entrypoint is
  uvicorn (FastMCP.streamable_http_app is native ASGI; gunicorn WSGI
  cannot serve it). Notes the polling-fallback behaviour and the
  retained-but-unused gunicorn.conf.py.
- docs/plans/2026-05-01-coda-mcp-server.md: prepend a SUPERSEDED
  banner. The shipped implementation is the v2 design in
  docs/mcp-v2-background-execution.md (3 tools on uvicorn+ASGI), not
  the 5-tool gunicorn+WSGI plan in this file. Kept for design-
  evolution archaeology.
- coda_mcp/mcp_endpoint.py: docstring now clearly states this module
  is a Flask Blueprint fallback for WSGI runtimes (gunicorn local dev,
  Flask test client). Production routes through coda_mcp.mcp_asgi.
Closes two coverage gaps surfaced by a pre-merge test audit. Both
files exercise surfaces that production traffic actually hits, and
neither had a dedicated test file before this commit.

tests/test_mcp_endpoint.py (9 tests, all pass)
- Pin the Flask Blueprint's JSON-RPC contract: initialize, tools/list,
  ping, tools/call (unknown), unknown method, CORS preflight,
  jsonrpc id echo, non-JSON body resilience, tool schema presence.
- Asserts the tool surface is exactly {coda_run, coda_inbox,
  coda_get_result}. Drift from the v2 contract fails loudly.

tests/test_coda_bridge.py (3 pass + 1 documented skip)
- Verify the bridge injects the Databricks Bearer token mounted via
  `databricks auth token` into Authorization on every forwarded
  request (regression guard — a silent drop would 401 every Genie
  Code call against a deployed app).
- Verify it surfaces server response bodies and refuses to run
  without CODA_MCP_URL configured.
- Skip and document the stdout-capture variant for a follow-up.

Full suite: 490 passed, 2 skipped (was 478/1 before this PR). No
regressions.
@datasciencemonkey datasciencemonkey self-assigned this May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant