# HTTP MCP and Secure MCP Tunnel The web app handles Streamable HTTP at `/mcp` on its existing listening port. It uses the MCP server SDK with a fresh server per HTTP request. Tools use the same SQLite database and SNS services as Research. There is no extra MCP listener, browser requirement, or nested Codex execution for external calls. ## Tools For the shared inbox tools, see [Activity MCP design](activity-mcp-design.md). Replies and chores share one activity model across Home, Messages, and MCP. | Tool | Input and effect | | ---------------------- | ----------------------------------------------------------------------------------------------------------------- | | `get_activity_context` | Optional `activityIds`, `cursor`, `limit`; proposals, effective content, user decisions and revision | | `publish_activities` | `requestId`, `expectedRevision`, `activities`; atomically persist task/reply proposals, preserving user decisions | | `list_connections` | `{}`; discover account IDs and status, without credentials | | `list_lists` | `connectionId`; discover Twitter or Mastodon lists | | `list_decks` | `{}`; saved deck IDs, titles, revisions and column counts | | `get_deck` | `deckId`; saved definition including column IDs and revision | | `create_deck` | `title`, `columns`, optional new `deckId`; immediately persist | | `replace_deck` | `deckId`, `expectedRevision`, `title`, complete `columns`; reject stale revisions | | `delete_deck` | `deckId`, `expectedRevision`; permanently delete | | `fetch_posts` | `connectionId`, `source`, optional `cursor`; read without saving a deck | | `fetch_column_posts` | `deckId`, `columnId`, optional `cursor`; read a saved column | Columns follow the [deck source contracts](webmcp-prototype.md). A deck has at most six columns. Preserve column IDs on edits. Stable deck and column IDs allow identical creation retries without duplicates. Updates replace the entire definition. Creation and deletion do not change another browser's selection; saved definitions refresh through the existing UI mechanism. Post retrieval returns up to 20 text records, source URLs, author information, and a continuation cursor. Reuse a cursor only with its original source/account. External tools are stateless and do not share the internal research turn's 12-request budget. No tool writes to an SNS account. Tool failures return `isError: true` and a structured error in text content. Tool input schemas must also work with non-JavaScript JSON Schema validators. Mastodon hashtag character restrictions remain enforced by the server and are described in the schema, without exporting JavaScript Unicode property escapes as a `pattern` that Python JSON Schema validators reject. Activity publication uses the same service as Home and Messages. Exact retries return the original receipt before revision checks; conflicting publications make no changes. Browser clients refresh on committed changes through the session-protected `/api/activities/events` stream. Reconnection also refreshes the saved snapshot. Source excerpts are supplied evidence, not a full synced conversation. Publishing a reply neither sends it nor marks it complete. ## Internal research The resident research chat connects Codex to `/mcp?research=`. Each turn registers its existing selected-account tools, temporary view, pagination budget, citation capture, and SSE updates. The registration expires on completion, cancellation, or startup failure; expired IDs return 404. External calls to `/mcp` access the persistent workspace tool catalog instead. Set `TWITTER_LITE_MCP_URL` to a local URL that reaches this same web process. The default is `http://127.0.0.1:${PORT:-3000}/mcp`. For example, when Vite uses port 3002, set `TWITTER_LITE_MCP_URL=http://127.0.0.1:3002/mcp`. The Codex app-server control connection remains stdio; MCP is HTTP. ## Access boundary MCP authentication is intentionally deferred. The exact `/mcp` endpoint bypasses browser owner/session checks; keep its listening address and reverse proxy private. Do not expose this unauthenticated endpoint on a public ingress. Browser pages and server functions retain their existing access checks. The two protected-resource discovery paths return 404 because OAuth is not configured. Unknown research IDs never fall through to workspace tools. ## Secure MCP Tunnel Create a dedicated tunnel in [Platform tunnel settings](https://platform.openai.com/settings/organization/tunnels). Use an installed `tunnel-client` or the [latest official release](https://github.com/openai/tunnel-client/releases/latest). Configure an HTTP target, then supervise the client with systemd. The [OpenAI guide](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels) describes runtime key permissions and ChatGPT workspace associations. ```sh tunnel-client init --sample sample_mcp_remote_no_auth \ --profile personal-workspace --tunnel-id tunnel_YOUR_ID \ --mcp-server-url http://127.0.0.1:3000/mcp tunnel-client doctor --profile personal-workspace --explain tunnel-client run --profile personal-workspace ``` Keep the runtime key in a private runtime file or environment, outside Git and the Nix store. Point the tunnel at the actual app bind address and port. The tunnel client has separate local health/readiness endpoints. Check readiness after restarting the app, then select the dedicated tunnel when adding a custom MCP server in ChatGPT. Authentication is None for this prototype. On UM790-Pro, dotnix declares the system service `tunnel-client-personal-workspace.service` in `systems/nixos/UM790-Pro/personal-workspace-tunnel.nix`. It forwards to the existing app at `http://100.91.91.87:3006/mcp`, reads its runtime API key through systemd `LoadCredential` from the existing sops secret, and serves local health checks on port 18791. The separate `tunnel-client-local-mcp.service` remains unchanged. The app's package revision and internal MCP URL are managed by dotnix's flake input and `homes/nixos/UM790-Pro/twitter-lite.nix`. ## Verification ```sh nix develop -c pnpm test nix develop -c pnpm typecheck nix develop -c pnpm test:e2e e2e/integrations/mcp-http.test.ts --project=desktop nix develop -c pnpm test:e2e e2e/integrations/activities.test.ts ``` The HTTP E2E uses the actual app middleware, an isolated SQLite database, and mock SNS relay. It checks anonymous MCP initialization, real saved-deck changes visible in Research, pagination entry points, stale-revision rejection, and continued protection of browser routes. Provider integration tests exercise HTTP calls, conversation resume and interruption through the real SDK provider with a fixture app-server.