Files
twitter-lite/docs/http-mcp.md
T

120 lines
7.2 KiB
Markdown

# 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=<registration-id>`.
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.