See every MCP tool call your AI agents make β and exactly where they fail.
A local-first debugger, flight recorder, and control layer for MCP.
npx octoport up --openYour agents call MCP tools all day. When something breaks β an expired OAuth token, a crashing server, a tool that silently changed its schema β you get a vague client error and zero visibility. octoport sits on the path of every MCP call and turns that darkness into a live, diagnosable record:
15:04:12 Claude Code β github / create_pull_request WRITE Β· MEDIUM β 381ms
15:04:18 Claude Code β supabase / execute_sql DATABASE_WRITE Β· MEDIUM β 4.2s AUTH Β· token_expired
15:04:30 Cursor β playwright / browser_click UNKNOWN Β· LOW β 12.7s
- Live Activity β every call, live: client, identity, server, tool, latency, risk badge, and a canonical failure diagnosis. The home screen.
- Failure diagnosis β every failed call is classified deterministically into
a canonical taxonomy (
AUTH Β· token_expired,NETWORK Β· dns,SERVER Β· circuit_open, β¦) instead of a raw stack trace. - Reliability β per server and per tool: success rate, p50/p95/p99, timeout and auth-failure counts, top failure categories, and deterministic anomaly alerts (consecutive failures, restart loops, latency spikes).
octoport doctorβ "why won't this MCP setup work?" answered with evidence and a concrete fix, in the CLI and the dashboard (same engine).- Tool surface integrity β every server's tool catalog is snapshotted and
diffed. A server that quietly grows
delete_repositorytriggers a risk-classified warning you must Accept β or Block the tool for every client with one click. - Local-first privacy β everything stays on your machine in SQLite.
Secret-looking argument keys are redacted before they ever hit disk, and
metadata-only mode (
logPayloads: false) never stores args/results at all. The dashboard always states which mode is active.
npx octoport up --openThe dashboard walks you through the rest:
- We found your MCP servers β octoport scans your Claude Code / Claude Desktop / Cursor / Windsurf / VS Code configs (global and project-scoped).
- Connect your AI client β copy one snippet per client.
- Make a test call β and watch it appear in Live Activity, live.
- Optionally clean up the now-duplicate direct entries (per-file preview, timestamped backups, restorable from Settings).
From then on your clients talk to one endpoint, and octoport sees everything:
add/edit/toggle servers centrally, and new servers propagate to every connected
client live (tools/list_changed), no restarts.
Until the npm package is published, run from source:
git clone https://github.com/nullarch/octoport && cd octoport && npm install && npm run build && node dist/cli.js up --open
Start at login:
octoport autostart # launchd (macOS) / systemd --user (Linux) / schtasks (Windows)
octoport autostart --removePort 6286 is "OCTO" on a phone keypad. Change it with octoport up --port <n>.
$ octoport doctor
SERVER
β github: error β AUTH Β· token_expired
The credential has expired β re-authenticate.
raw: Error POSTing to endpoint (HTTP 401): Unauthorized
fix: Reauthorize "github": revoke the stored grant (Servers β github β Grants)
and authorize again, or refresh the credential in its headers/env.
Doctor checks the environment (Node, config permissions), your clients
(detected configs, whether they point at the hub, whether a session ever
connected), every server (status, classified failure, stderr tail, last
successful call), and the gateway (tokens, privacy mode, policy, IdP
reachability). --json for scripts; the dashboard's Doctor tab runs the same
engine.
Under the observability layer, octoport is a full MCP gateway β that's why it sees every call:
- One endpoint β N clients Γ M servers becomes 1 Γ M.
- Process supervision β stdio servers run as child processes with crash isolation, jittered backoff restart, start/call timeouts, a circuit breaker after 8 straight failures, and a per-server stderr ring buffer.
- Hosted servers with OAuth β add a hosted MCP URL; if it wants OAuth the
server card shows Authorize (PKCE, one-time state, dynamic client
registration). Tokens live in your config dir,
0600. - Full catalog aggregation β tools, prompts (
server__prompt), and resources (URIs pass through, reads routed to the owning server). The call log records all three kinds. - Server-initiated requests relayed β
sampling/elicitation/rootsanswered by the client whose call is executing on that server. - Named access tokens β one per person or machine; revoking kills live
sessions on the spot. Scopes:
admin(dashboard) vsmcp(endpoint). - Local security by default β
127.0.0.1binding, Host/Origin validation (DNS-rebinding protection), per-install bearer token. The MCP Inspector RCE (CVE-2025-49596) happened because a localhost tool skipped exactly these. - Blocked tools β a per-server denylist (globs) enforced at
tools/listand on every call; the "Block" button on surface-change warnings writes it.
octoport never exposes itself beyond loopback unless you say so, twice.
Recommended: Tailscale. Keep the default loopback bind, or bind the tailnet address; nothing touches the public internet:
octoport up --host 100.x.y.z --allow-host hub.tailnet-name.ts.net --behind-proxyReverse proxy (Caddy) with a real domain. octoport does not terminate TLS β your proxy does:
# Caddyfile
hub.example.com {
reverse_proxy 127.0.0.1:6286
}
octoport up --allow-host hub.example.com --behind-proxy --public-url https://hub.example.com--public-url makes snippets, the dashboard, and OAuth callbacks use the
external address; the dashboard WebSocket switches to wss:// on HTTPS.
GET /healthz answers without a token for proxy health checks. A non-loopback
bind refuses to start without --allow-host and --behind-proxy;
X-Forwarded-Host is deliberately ignored.
Small team setup: run octoport on a shared box behind Tailscale, one named token per teammate, and Live Activity becomes a team-level "which agent did what" record.
Status: functional, not yet pilot-verified. The organization gateway is feature-complete for a first pilot and covered by the test suite (including user-boundary attack repros), but has not yet been run end-to-end against a real IdP and real MCP clients in production. Treat it as alpha. Personal mode is stable.
One MCP endpoint for every employee: IT registers servers once, employees add one URL and sign in with company SSO, access follows group profiles, per-user services are authorized individually, and every call is centrally audited under a verified identity.
Company SSO (generic OIDC).
Tokens validate against the IdP's JWKS (discovery, cache, rotation; RS256/384/
512 + ES256/384; iss/aud/exp/nbf enforced). Unauthenticated requests get a
WWW-Authenticate challenge pointing at
/.well-known/oauth-protected-resource (RFC 9728). Add loginClientId for a
browser Sign in with SSO flow (/login, Authorization Code + PKCE).
Disable the account at the IdP and access dies β octoport has no user database
of its own, on purpose.
Profiles (group-based access). Once any profile exists the MCP surface is
default-deny. Enforced at tools/list and on every call; denials are logged.
Settings has a policy simulator ("what would this identity see?").
"profiles": {
"backend-engineer": {
"groups": ["engineering"],
"servers": ["github", "jira", "sentry"],
"denyTools": ["kubernetes__delete_*"] // globs on public names; deny wins
},
"ci": { "tokens": ["github-actions"], "servers": ["github"] }
}Per-user OAuth downstream. "credentialMode": "per_user_oauth" gives
every identity its own connection and OAuth grant β employee A's GitHub is
never employee B's GitHub. Employees self-serve at /my; admins can
revoke any grant (kills its live connection). Secrets can be references
(file://β¦, vault://β¦) instead of literals.
Central audit. Every row records the verified subject, including policy
denials. Admin actions get their own event trail (GET /api/events, Audit
tab); GET /metrics serves Prometheus.
Deployment: Dockerfile + deploy/docker-compose.yml
(+ a Keycloak demo); guides in
docs/deploy.md and docs/idp.md; design in
docs/threat-model.md.
| Command | What it does |
|---|---|
octoport up [--open] [--port <n>] [--host <addr>] [--allow-host <h>] [--behind-proxy] [--public-url <url>] |
Start the hub |
octoport doctor [server] [--json] |
Diagnose client/server/gateway problems, with fixes |
octoport autostart [--remove] |
Start at login (launchd/systemd) |
octoport import [--dry-run] |
CLI import (the wizard does this too) |
octoport connect [client] |
Print connection snippets |
octoport logs [--limit <n>] [--jsonl [file]] |
Inspect or export the call log |
octoport token [--rotate] |
List tokens / rotate the default token (live rotation: dashboard β Rotate) |
- Local-first by default β SQLite on your machine,
0600. - Metadata-only is first-class:
logPayloads: falsestores no args/results. - Secrets are redacted before persistence, in every mode.
- The dashboard always states exactly what is stored.
- Catalog snapshots store hashes, not tool descriptions/schemas.
- Not a finished enterprise governance product. SSO, profiles, per-user OAuth, and verified audit exist (see Organization mode), but there's no SCIM, no tenant separation, and no compliance claims yet.
- Not agent mind-reading. octoport sees transport truth β what was called, what failed, what changed. It does not claim to know why a model chose a tool; that requires client-side context it doesn't collect today.
- Not a context optimizer for Claude Code. Claude Code ships native MCP tool search; octoport doesn't compete with it.
- Not magic. Tool names are namespaced
server__tool(capped at 48 chars with deterministic hashing) and routed through a mapping table β never string-parsed back.
- Server-initiated relay (sampling/elicitation/roots) on a shared server
uses in-flight correlation, and refuses when two identities call it at once
(mark such servers
per_user_oauthto use those features safely). - Catalog snapshots/diffs cover shared servers; per-user servers can legitimately show different surfaces per identity and are not snapshotted.
- Config edits outside the dashboard need a restart (dashboard changes apply hot); rotate a token live from Settings β Rotate.
autostartrequires a permanent install (npm i -g octoportor a repo checkout) β refuses to point launchd at an ephemeral npx cache.- Personal-mode tokens are full-admin by default; org mode uses scoped
(
admin/mcp) tokens, per-user OAuth, and profiles.
MIT-licensed, provided as is β a solo-maintained project, so no response-time promises. Issues and PRs are welcome and read; small, focused PRs with tests have the best odds. If you need guarantees, fork freely.
npm install
npm test # 207 tests: unit + E2E incl. mock-OAuth, mock-OIDC, mock-Vault
npm run build
node dist/cli.js up --openThe telemetry/event schema is documented in docs/events.md. Product direction and priorities live in PIVOT.md (it supersedes ROADMAP.md for ordering); the organization-gateway details stay in ROADMAP.md.