@extrovert.dev/mcp
v0.1.8
Published
Extrovert MCP server - a real mailbox for your agent, in one call. Tools incl. a blocking wait_for_email with OTP/link extraction.
Downloads
4,568
Maintainers
Readme
Extrovert MCP server
A real inbox for your agent, in one call.
@extrovert.dev/mcp is the Model Context Protocol server for
Extrovert: Message Science's agentic-email platform. It gives an AI agent a real,
persistent inbox on a platform or customer domain, with explicit permission to create, read, send,
and administer. Hosted OAuth connections carry the resource reach and actions chosen during
consent; existing scoped agent keys retain their fixed resource ceilings. Call whoami to inspect
the actual connection, its reach, actions, and expiry before starting work.
Distribution: default npm commands use stable;
@nextis opt-in preview. Extrovert also operateshttps://mcp.extrovert.dev/mcpas a stateless Streamable HTTP endpoint with browser OAuth. The same package installsextrovert-mcpfor transports andextrovertfor supported setup, authentication, mailbox, review-status, and reviewed-send commands.
wait_for_email waits for a matching incoming message and returns it with any
OTP code and verification link already extracted. Use it to continue a sign-in or verification flow.
redeem an enrollment key -> create_inbox -> use it as a sign-up address -> wait_for_email -> { otp_code, verification_link }Start and stay current
Tell your agent:
Read https://docs.extrovert.dev/llms.txt, connect Extrovert using my existing account if I have one, and help me send my first email.
Prefer hosted OAuth in compatible hosts. extrovert setup --host auto detects an unambiguous
Codex, Claude Code or Hermes runtime and prefers hosted setup; unsupported or ambiguous environments
receive a native setup handoff. Explicit host selection retains the stdio default. Existing entries
and profile credentials are preserved. Configuration is complete only after sign-in and whoami
in the actual MCP session.
Call agent_context on first Extrovert use in a session, after an hour, and after schema errors.
Without connected tools, read https://mcp.extrovert.dev/.well-known/agent-contract.json or the live
agent guide. The CLI exposes extrovert version --json and extrovert agent status --json.
These checks never update files or authenticate, and status does not inspect installed skill files.
Unqualified commands resolve stable; @next selects preview. --prefer-online requests fresh registry metadata.
Resolution does not restart an already running MCP process. Respect pinned versions and local edits; update only Extrovert skills in their original scope when
allowed. Updating files does not reload an active skill or local stdio process.
Current signup availability comes from the live context. Use an existing account first; new signup requires a supplied human email and human verification. Follow the returned activation method: incoming-email proof asks the human to send to the reserved address; only an actual legacy OTP response asks for a code. See agent updates.
Remote MCP needs no local runtime. The packaged CLI needs Node >=20 but no compiler or source checkout. Read the task index to choose a focused guide; installed skills include their own task-specific references.
Quiet watches and durable Tasks
extrovert review watch --wait-seconds 86400 --json quietly repeats short API waits
until attention arrives, no reviews remain, or its deadline is reached. The default
is 30 minutes. It does not acknowledge feedback, revise drafts, approve, or send.
Keep the host turn active and collect its process result, then handle the event and
watch again. verify --wait-seconds 86400 similarly resumes pending signup.
For clients negotiating the 2026-07-28 MCP Tasks extension, the existing
check_activation and wait_for_review_event tools return durable observer handles.
Use tasks/get and tasks/cancel; ordinary clients retain direct tool results and
bounded waits. No general client-support claim is implied. Completion means the
observer result is available, not that an account was verified or email sent.
Cancellation/expiry stops observation only. No connector independently wakes a
closed chat. Waiting contract.
Tools
| Tool | What it does |
|---|---|
| agent_context | Read the hosted release, skill versions/digests, signup availability and current guides. |
| redeem_enrollment | Exchange an enrollment token (pk_enroll_...) for a scoped agent key (pk_agent_...). |
| create_inbox | Create an inbox. Paid accounts use extrovertmail.com; free signups use free.extrovertmail.com. Attach arbitrary metadata. |
| list_inboxes | List readable inboxes within the connection grant or legacy key ceiling. Broader connections can narrow the selection; legacy org keys must choose project:<id> or wildcard:true. |
| get_inbox | Fetch one inbox by opaque inbox_id (pmbx_...) or address (with its metadata). |
| update_inbox | Update settings; daily_send_limit (1-10,000) sets the enforced rolling-24-hour recipient cap and requires opt-in mailbox:quota. |
| delete_inbox | Permanently delete an inbox, its messages, and sender identity. Requires mailbox:delete; cannot be undone. |
| send_email | Send a new email via the inbox's authenticated sender. |
| reply_email | Reply within an existing thread. |
| read_messages | List messages in an inbox (optionally unread-only). |
| list_threads | List conversation threads with cursor pagination. |
| search_threads | Search conversation summaries by subject, participant, or snippet. |
| get_thread | Read oldest-first source bodies, explicit incomplete-body IDs, and the composition context version. |
| delete_thread | Move every message in a thread to Trash, or permanently expunge it. |
| search | Full-text search across one or all inboxes. |
| wait_for_email | Wait for a matching message; return it with any extracted otp_code / verification_link. |
| quote_domain | Return a short-lived registration and renewal quote without reserving, charging, or registering. |
| request_domain_purchase | Create an idempotent, durable domain-purchase request for human approval. |
| request_plan_change | Create an idempotent upgrade or downgrade request for human approval. |
| get_commerce_request | Read the exact blocker, approval URL, payment state, progress, and next safe action. |
| list_commerce_requests | Recover and list this agent's purchase and plan requests. |
| whoami | Confirm the connected agent, organization, project, and available actions. |
| get_domain | Answer whether a domain is ready, who needs to act, and how many inboxes this connection can see. |
| verify_domain | Recheck the customer's nameserver entries now and return the latest readiness result. |
| wait_for_domain | Wait for readiness for a bounded interval, then return a clear resumable outcome. |
| list_domain_events | Resume domain updates using the previous cursor, including ready, action-needed, and recovery events. |
Every tool is registered with a typed zod input schema and behavioural
annotations (readOnlyHint, destructiveHint, ...) so hosts can present and gate them correctly.
Access and delegation
This source supports two hosted profiles sharing the same implementation. /mcp
and local stdio retain the complete toolset. /assistant/mcp is the OAuth-only
directory candidate: 69 email, review, writing-rule, inbox, and owned-domain tools,
with selected-inbox/project consent and existing entitlements. It cannot purchase,
upgrade plans, export credentials, configure webhooks, or perform broad account
administration. The profile is enforced by the API grant as well as tool discovery.
Availability in a public directory is a separate release/review step, not implied
by this source package. Skills installation does not complete host-owned OAuth.
Hosted OAuth uses an explicit connection grant. Choose Personal assistant or a Dedicated agent, then choose selected inboxes, a project, an organization, or Full account control. Resource reach and permitted actions are separate. Current human authority remains the upper bound; public connections never gain private operator access.
For ordinary email setup, choose Dedicated agent, Selected inboxes, and Read and send. Personal assistant can also have limited access. See the installation guide.
Full account control is intended for explicitly requested account administration. It can use other agents' inboxes, change access and policies, create credentials, and approve requests - including its own. The default is 24 hours; Until revoked is an explicit alternative. Refresh never extends the original deadline. Credentials created during setup, including administrative credentials, survive independently until separately expired or revoked. See Connections in the account menu to review activity and revoke the parent or its created access separately.
Existing scoped agent keys remain available for unattended workers. Their scopes, resource ceiling, expiry, and revocation still apply. Knowing an inbox address never grants access. Do not automatically substitute credentials after expiry.
For the complete setup-to-worker handoff, identity comparison, expiry recovery, and list/read
troubleshooting, see Connections and access.
Start with whoami to verify the connection's project and permissions, then search
and describe the relevant action before passing its exact path, query, and body inputs.
adminMe requires Full account control and is unnecessary for project managers.
The packaged CLI supports extrovert admin actions, admin describe <action-id>,
admin read <action-id> --input '<json>', and admin change <action-id> --input-stdin.
The advertised admin command failed at executable dispatch before 0.1.0-pre.35;
update an affected installation before troubleshooting authentication.
Project-wide inbox visibility is expected for managers. Before issuing a dedicated
worker key, inspect listAgentKeys for the existing persona and verify its installed
credential. Listed secrets cannot be recovered; issue a replacement only when needed.
Give a manager one project
Choose either setup path:
- OAuth: connect your MCP host or run
extrovert auth login. In the browser, choose Project, select the project, then choose Project manager and review the authorization. For workers that send mail, choose Custom and include Send mail alongside the manager permissions. - Key handoff: in the console, select the organization/project and open
Credentials -> API keys -> Create project manager key. Name it, choose an expiry,
optionally allow sending, and copy the one-time secret into
EXTROVERT_API_KEYfor local MCP, CLI, or SDK use. Hosted MCP uses OAuth. No prior OAuth connection is needed for this human-admin creation path.
An already authorized manager can create another project manager credential through
change_administrative_action with action_id: "createConnectionCredential".
First inspect describe_administrative_action for its exact schema. Use its own
connection ID, project reach, the same organization/project, and explicit
agent:manage / credential:delegate scopes plus the actions the child needs.
The child can receive only permissions the parent holds. The new human-only
manager-keys endpoint is deliberately excluded from MCP; delegation must retain
its parent connection and permission ceiling.
Both paths create the same project boundary. Managers can create personas, inboxes, and restricted worker credentials. Ordinary persona keys/enrollment keys cannot become managers by requesting administrative scopes. Workers survive parent expiry or ordinary revocation; Connections -> Also revoke all workers stops the team. See Connections and access for the complete handoff and revocation walkthrough.
Connect with hosted OAuth
Give an OAuth-capable MCP client this URL:
https://mcp.extrovert.dev/mcpThe endpoint publishes RFC 9728 protected-resource metadata and Extrovert authorization-server
discovery at https://api.extrovert.dev. Compatible clients open the browser sign-in and consent flow, then store and refresh the
grant. Existing scoped pk_agent_... bearer keys also work when a client is configured explicitly.
The hosted service runs MCP SDK v2's fresh-server-per-request handler: no process-local session map, sticky routing, or session teardown is required.
Preview access
Default commands below use stable. To try an early feature or fix, run
npx --yes --prefer-online @extrovert.dev/mcp@next --help. For a new local stdio
entry, also pass setup --host codex --transport stdio --channel next (or choose
Claude/Hermes). Running a preview CLI alone does not change existing entries.
Supported Claude refresh accepts --channel next to opt in and --channel latest
to return; ordinary refresh preserves the saved channel. Pins still require a
manual change. Restart the host connection and verify whoami afterward.
See preview access
for existing installations, SDKs, compatibility checks and hosted/plugin boundaries.
Install and run
Use the default stable package:
# stdio for an MCP host
npx -y @extrovert.dev/mcp
# inspect the packaged CLI
npx -y @extrovert.dev/mcp --help
# register the stdio server in Codex or Claude Code
npx -y @extrovert.dev/mcp setup --host codex
npx -y @extrovert.dev/mcp setup --host claude
npx -y @extrovert.dev/mcp setup --host hermesFor reproducible environments, append an exact published version to the package name. The package installs the
extrovert-mcp and extrovert aliases over one entrypoint; there is no second package or transport
implementation to keep in sync.
Before refreshing, inspect the existing manager, scope, version pin and local edits through supported private helpers. Do not dump environment variables or raw configuration, even with filtering or redaction. Preserve deliberate pins: an unpinned helper executes downloaded code even if it leaves the saved pin intact. Follow the safe update checklist. Only for an unpinned Claude Code stdio installation whose policy permits this helper, run from its project root:
npx --yes --prefer-online @extrovert.dev/mcp setup --refresh --host claude --jsonRefresh privately updates one supported local/user npx entry, preserves its stable or preview
channel, and puts --prefer-online before the package argument. It preserves every other
argument, environment value and credential, and saves a private backup when changing the file.
An entry already using both needs no write. Pins, custom launchers, project-scoped entries,
busy files and ambiguous scopes return manual_required; inspect those configurations privately
without putting credentials in commands or chat. A restart_required result means Claude must
restart its Extrovert MCP connection, then check the live release and call whoami.
This command does not restart the host or reload installed skill instructions.
auth whoami is an alias for the local profile's whoami command; both support
--json and neither verifies a separate hosted MCP session.
CLI fallback
The CLI uses the same typed client as MCP and prints ordinary message text without a curl | jq
pipeline:
For an existing account, use browser sign-in in the intended local profile:
export EXTROVERT_PROFILE=support
extrovert auth login
extrovert whoami
extrovert inbox list
extrovert message list --inbox [email protected]auth login verifies and reuses working profile access first. Otherwise it opens local browser
sign-in and explicit consent when available. Its printed fallback URL returns to Extrovert's website
with a one-use completion code, so it also works on another machine. SSH/headless sessions use this
hosted completion path directly. In an interactive terminal, paste the code at the hidden prompt.
For automation:
extrovert auth login --no-browser --json
# When pending, the person opens authorization_url and approves access.
extrovert auth complete --json
# Supply the website's completion code on private stdin in this same profile.
extrovert whoamiNever put a completion code or key in command arguments, chat, or logs. pending is not connected;
wait for complete, then verify whoami. auth cancel clears pending login state while preserving
existing credentials. Use auth login --reconnect for deliberate new consent. Requests expire after
10 minutes. See the login guide.
Local OAuth is saved privately and refreshed within the original consent grant: identity, resource
reach, actions, and expiry remain bounded. Start or reload the matching stdio MCP process and call
whoami there. Hosted MCP OAuth belongs to the host; local login or doctor does not verify it.
Enrollment is also supported: extrovert enroll --agent-handle support reads a hidden prompt or
EXTROVERT_ENROLLMENT_KEY. Existing agent keys and independently issued API credentials use
extrovert auth login --with-token with hidden stdin. Use these deliberately; do not create another
customer account or borrow another agent's credential to repair access.
On Unix the credential directory is mode 0700 and credential files are mode 0600.
EXTROVERT_CONFIG_DIR selects an explicit directory; otherwise Hermes uses its own
HERMES_HOME/extrovert directory, and other runtimes use the platform config directory.
EXTROVERT_PROFILE separates agents within that base. An explicit EXTROVERT_API_KEY takes
precedence; remove that override from the intended environment before new browser consent.
Keep the same profile and API environment throughout a pending login.
For Hermes hosted OAuth, use extrovert setup --host hermes --transport hosted, then
hermes mcp login extrovert. Finish browser consent, reload MCP through Hermes, and call whoami.
If approved OAuth fails, keep the non-secret request ID and report the failed step instead of
repeating consent or creating another account. Read live context for signup availability.
Finish a new signup in the same Hermes session
extrovert signup --human-email [email protected] --username coleman --display-name Coleman
prints the incoming-email instructions and watches for ownership proof for up to 30 minutes.
Each status request waits at most 55 seconds. If it times out, retain the selected profile and
resume with extrovert verify --wait-seconds 86400; no second signup is needed.
At proof, Extrovert queues a welcome and one practice draft owned by the signup agent. Verification
and whoami return its stable review ID, account-aware link and optional coaching prompt. Recover
that review instead of composing another hello. Console sign-in is separate from inbox ownership.
Local stdio MCP and CLI read the same profile, including the pending-to-durable key exchange. While Hermes loads its native MCP catalog, continue through the CLI in the active turn:
extrovert tool describe get_review
# Supply JSON with the returned review id on stdin.
extrovert tool call get_review --input-stdin --jsonThe bridge exposes the same review tool schemas and handlers for reading feedback, learning an
Extrovert writing rule from an authenticated human source turn, reading rules back, revising the
same review and waiting for its outcome. Use tool describe before constructing each input.
It grants no extra permissions. Hermes reloads changed MCP configuration while idle when automatic
reload is enabled; /reload-mcp is the manual fallback. A full Hermes restart is unnecessary.
Verify native MCP whoami separately once those tools appear.
Is my domain ready?
extrovert domain status mail.example.com
extrovert domain recheck mail.example.com
extrovert domain wait mail.example.comStatus leads with a plain-language answer: whether you need to change DNS, whether Extrovert is
finishing setup, or whether you can create/use inboxes. Ready includes scoped inbox counts and
the next action. Technical verification/signing fields are diagnostics, not readiness evidence.
Automatic setup continues after the agent disconnects. A disconnected agent must resume status
checks or its event cursor to receive updates; a bounded wait does not promise a later callback.
Build and run from source
Requires Node >= 20. From this directory:
pnpm install --frozen-lockfile
pnpm run build # compiles to dist/ (excludes tests)Two transports, one binary:
# stdio: for local hosts (Claude Desktop, Claude Code, Cursor)
node /absolute/path/to/extrovert/mcp/dist/bin.js
# self-hosted stateless Streamable HTTP at /mcp (default :8787)
node /absolute/path/to/extrovert/mcp/dist/bin.js --http --port 8787Run it without building during development:
pnpm run dev # tsx watch, stdio
pnpm run dev -- --http # tsx watch, HTTPConfiguration (environment)
| Variable | Default | Purpose |
|---|---|---|
| EXTROVERT_API_BASE_URL | https://api.extrovert.dev | Base URL of the Extrovert REST API. |
| EXTROVERT_API_KEY | (empty) | Scoped agent key (pk_agent_...), independent API credential (ev_credential_...), or local enrollment key (pk_enroll_...). |
| EXTROVERT_CONFIG_DIR | platform config directory | Override the local credential directory. |
| EXTROVERT_MOCK | (off) | Set 1 to force offline fixtures. |
| EXTROVERT_REQUEST_TIMEOUT_MS | 30000 | Per-request timeout for non-blocking calls. |
| EXTROVERT_MAX_WAIT_MS | 300000 | Maximum time wait_for_email can wait. |
| EXTROVERT_MCP_OAUTH_ENABLED | (off) | Require consent-bound Extrovert OAuth or an introspected agent key on HTTP. |
| EXTROVERT_MCP_OAUTH_ISSUER | https://api.extrovert.dev | Extrovert OAuth authorization-server issuer. |
| EXTROVERT_MCP_PUBLIC_URL | https://mcp.extrovert.dev/mcp | Public RFC 9728 resource identifier. |
| PORT / HOST | 8787 / 0.0.0.0 | --http bind. |
Offline fixtures are opt-in. With no
EXTROVERT_API_KEY, the server still talks to the live API. Check live signup availability first. When enabled, an agent can start withsign_upand receive a short-lived limited key in-session. The key expires with its activation reservation and is revoked whenverify_signupreturns its replacement. For anincoming_emailresponse, ask the human to email the reserved inbox, callcheck_activationwithwait_seconds: 55for a bounded watch, thenverify_signupwithout an OTP once proven.correct_activation_emailrequires the current revision and a fresh matching email; it does not extend expiry. Previously issued OTPs remain supported until their original expiry. SetEXTROVERT_MOCK=1to use deterministic in-memory fixtures;create_inbox,send_email, andwait_for_emailthen operate on one coherent offline dataset.
Host configuration
Point any stdio-capable host at the default stable package:
{
"mcpServers": {
"extrovert": {
"command": "npx",
"args": ["-y", "@extrovert.dev/mcp"]
}
}
}For Codex or Claude Code, register that same local entrypoint:
npx -y @extrovert.dev/mcp setup --host codex
npx -y @extrovert.dev/mcp setup --host claudeOffline: omit
EXTROVERT_API_KEYand setEXTROVERT_MOCK=1; the packaged server uses deterministic fixtures with no network or mail.
Example agent flow
The canonical flow: redeem -> create_inbox -> wait_for_email: as an agent would run it.
1. Redeem an enrollment key for a scoped agent key. Skip this if the host already has a key in
EXTROVERT_API_KEY.
// tool: redeem_enrollment
{ "enrollment_token": "pk_enroll_42_aZ9...", "agent_handle": "signup-bot" }
// -> { agent_id, agent_key: "pk_agent_... (shown once)", scopes: ["mailbox:create", ...],
// org_id, project_id }2. Create an inbox. Omit username and domain to use the account's shared domain.
Shared local parts must normalize to at least five characters and cannot use a reserved name.
Optionally tag it with arbitrary metadata (string/number/boolean values).
// tool: create_inbox
{ "display_name": "Signup Bot", "metadata": { "team": "growth", "vip": true } }
// -> { object: "inbox", id: "pmbx_... (opaque inbox_id: treat as opaque)",
// org_id, project_id, address: "[email protected]", status: "live",
// sender_verified: true, metadata: { "team": "growth", "vip": true } }Addressing an inbox. Every inbox-keyed tool's
inboxargument takes the canonical opaqueinbox_id(pmbx_...) or the inbox's email address as a within-project alias. The id is the stable key; treat it as opaque (do not parse the prefix). Each inbox carries its fixedorg_id/project_id(a key only ever touches inboxes in its bound project).
Inbox metadata is a shallow-merged map. On
update_inbox, pass an object to merge keys in (a key whose value isnulldeletes it), pass a top-levelnullto clear all metadata, or omitmetadataentirely to leave it untouched. Reads always return an object ({}when empty).
3. Use the address to sign up somewhere (the agent fills a form, hits an API, etc.), then wait for the verification email and read the code straight out of the result:
// tool: wait_for_email
{ "inbox": "[email protected]", "from": "stripe.com", "subject": "verify", "timeout_ms": 120000 }
// -> { matched: true,
// message: { from, subject, text, ... },
// otp_code: "481920",
// verification_link: "https://dashboard.stripe.com/verify?code=481920&id=evt_9" }Use the returned OTP code or verification link to continue.
4. Keep working. Before composing, inspect the inbox's effective review policy,
precheck the intended recipients, and read get_rules for the selected category
without a scope filter. Apply those rules and retain the returned
composition_token. The placeholders below must be replaced with actual returned
values; do not fetch a token merely to submit text written before reading rules.
For a reply, read get_thread first. If text_context_complete is false, read
every incomplete_message_ids entry with get_message using variant: "source"
before composing. Check pending reviews for that inbox/thread without composer=me
and coordinate existing work instead of creating another response. Retain the
thread's context_version and read fresh rules for this separate reply.
// tool: send_email
{ "inbox": "[email protected]", "to": ["[email protected]"],
"subject": "intro", "text": "Hi: provisioned via Extrovert.",
"category_id": "<selected category id>", "composition_token": "<fresh send rules token>",
"intent": { "summary": "Introduce the new agent inbox." }, "client_id": "send-intro-1" }
// tool: reply_email
{ "inbox": "[email protected]", "thread_id": "thr_...", "text": "Following up.",
"category_id": "<selected category id>", "composition_token": "<fresh reply rules token>",
"expected_context_version": "<context_version from get_thread>",
"intent": { "summary": "Continue the existing conversation." }, "client_id": "reply-followup-1" }
// tool: search
{ "query": "invoice", "inbox": "[email protected]" }Retain each submission/review ID and stable retry key. A queued response is not sent: share its review link, handle feedback on that same draft, and continue observing until the actual outcome is established. On a context conflict, reread and reconsider the draft; do not simply attach a new token to old text. Review and recovery contract.
Stateless HTTP notes
extrovert-mcp --http speaks MCP Streamable HTTP:
POST /mcp: one authenticated client->server request, served by a fresh MCP server instance.GET /mcpandDELETE /mcp: legacy stateless compatibility responses; no session is retained.GET /healthz: liveness, version, transport, and authentication mode.GET /.well-known/oauth-protected-resource/mcp: RFC 9728 protected-resource metadata when OAuth is enabled.
Each request gets an isolated server + client and emits no mcp-session-id, so requests can land on
any service instance. Hosted MCP requires Authorization: Bearer ... with an MCP-audience Extrovert
OAuth access token or an existing scoped pk_agent_... key. The API rechecks the grant, expiry,
revocation, current human roles, and resource/action boundaries. Independent ev_credential_...
credentials are API-only: use them through local stdio/CLI or an SDK, not as hosted MCP bearer tokens.
The raw bearer token is never persisted by MCP or API.
EXTROVERT_API_KEY and unauthenticated fixture mode remain local/self-hosting conveniences only;
the production service refuses to start without OAuth enabled.
Architecture
src/
bin.ts CLI entrypoint (--http | stdio)
cli.ts setup/auth/signup/mailbox/review CLI using the typed client
credentials.ts permission-restricted, atomic local credential persistence
server.ts McpServer factory + instructions
stdio.ts stdio transport
http.ts stateless Streamable HTTP transport (Express, OAuth + scoped keys)
auth.ts consent-bound OAuth exchange, RFC discovery, and agent-key introspection
tools.ts manifest-driven tools: zod schemas, annotations, handlers, registration
client.ts thin typed Extrovert REST client (one method per /v1 endpoint)
config.ts env-driven configuration
types.ts Extrovert resource types (the REST wire shapes)
extract.ts OTP code + verification-link extraction (ported from Go)
fixtures.ts offline fixture store for tests and demos
extract.test.ts unit tests for the extraction logicThe typed client (ExtrovertClient) is the single network seam: tools never call fetch
directly. OTP/link extraction is shared by the MCP wait tool and its offline fixtures.
Live API and fixtures
The MCP client talks to the Extrovert Go REST API by default. When EXTROVERT_MOCK=1,
ExtrovertClient returns fixture data instead so tests and offline demos can exercise the same
tool surface without network access. Offline domains remain waiting_for_dns: fixtures do not
check real DNS or run background setup, and their event pages stay empty while preserving the
supplied cursor. Recheck never fabricates confirmation. The endpoints the client targets:
| Tool | Method + path |
|---|---|
| redeem_enrollment | POST /v1/enroll |
| create_inbox | POST /v1/inboxes (project-tier sugar) / POST /v1/projects/{project_id}/inboxes |
| list_inboxes | GET /v1/inboxes (project sugar) / GET /v1/projects/{project_id}/inboxes / GET /v1/projects/-/inboxes (org wildcard) |
| get_inbox | GET /v1/inboxes/{inbox_id} |
| update_inbox | PATCH /v1/inboxes/{inbox_id} (set daily_send_limit with mailbox:quota) |
| delete_inbox | DELETE /v1/inboxes/{inbox_id} |
| send_email | POST /v1/inboxes/{inbox_id}/send |
| reply_email | POST /v1/inboxes/{inbox_id}/reply |
| read_messages | GET /v1/inboxes/{inbox_id}/messages |
| list_threads | GET /v1/inboxes/{inbox_id}/threads |
| search_threads | GET /v1/inboxes/{inbox_id}/threads/search |
| get_thread | GET /v1/inboxes/{inbox_id}/threads/{thread_id} |
| delete_thread | DELETE /v1/inboxes/{inbox_id}/threads/{thread_id} |
| search | GET /v1/inboxes/{inbox_id}/messages/search (fans out across inboxes when none given) |
| wait_for_email | POST /v1/inboxes/{inbox_id}/wait |
The path key is the canonical opaque inbox_id (pmbx_...); the inbox's email address is accepted
as a within-project alias. Scope is in the KEY (no scope headers): a pk_agent_proj_... key's
project is implicit; a pk_agent_org_... key reaches its org subtree and must pick a list breadth
(project/wildcard): a bare org-key list is a 400 breadth_required. Errors are RFC-9457
problem+json (application/problem+json) with a closed machine code
(forbidden_scope, breadth_required, not_found, idempotency_conflict, ...); the client surfaces
that code (and any request_id) on every tool error, in both live and EXTROVERT_MOCK=1 modes.
Scripts
pnpm run build # tsc -> dist/ (production build, tests excluded)
pnpm run typecheck # tsc --noEmit over the whole project (incl. tests)
pnpm run test # node:test via tsx
pnpm run dev # tsx watch (stdio); add -- --http for HTTP
pnpm run start # node dist/bin.js
pnpm run start:http # node dist/bin.js --httpMIT (c) Message Science. Extrovert is steel-at-dusk: a dark, technical developer brand whose single warm signal is the amber seam of a side-gate.
Signup accepts display_name for the name people see next to the agent address;
username chooses the address itself. The CLI uses signup --display-name "Coleman".
A verified incoming claim triggers an Extrovert welcome with the sender name, address and plan.
The verification response supplies the first-message handoff and current onboarding guidance.
Show the human the review link before waiting for approval. They can approve, edit, or coach
the agent, which saves reusable feedback as writing rules. End setup with a brief explanation
of the connection's actual whoami capabilities and an offer to explore Extrovert together
through human sign-in and explicit consent for broader access.
Inbox capacity errors
inbox_limit_exceeded identifies the billing account inbox cap shared across its organizations
and projects. enrollment_token_mailbox_budget_exhausted identifies the enrollment key lifetime
creation allowance shared by its agents. A key showing 5/7 can still hit a full billing account.
Deleting unused inboxes frees account capacity but does not refund key usage; increasing a key
allowance does not increase account capacity. These are inbox counts, separate from sending limits.
Read the error message and quota counts before requesting a plan change. See
Rate limits and quotas.
Product support
Check cases with list_support_cases {} in MCP or support.cases.list() in the
TypeScript SDK. Get or reply using only the case ID. Ask for help with a title
and description; authenticated context supplies an unambiguous project. Use
get_support_context if the destination is ambiguous. Project-first SDK signatures
and project-prefixed HTTP routes remain compatible.
MCP, CLI and the TypeScript SDK generate retry IDs once before sending. Preserve the exact recovery request on uncertain errors. If the whole response is lost, check existing reports before a new create. Raw HTTP requires explicit client_id. Append-only replies can omit expected_version; resolve/reopen require it.
support:submit files and follows own/shared reports. Explicit support:read and
support:write cover other reports within granted resource limits; project managers
include all three. Automatic feedback remains opt-in, and cases require an explicit
request. Feedback alone records evidence without opening a conversation.
CLI: extrovert support cases list --json, support cases create --input-stdin,
and support cases reply CASE_ID --input-stdin share the MCP handlers.
For an update check, use MCP agent_context or whoami in the active conversation.
Executing runtime facts are separate from hosted releases and fresh CLI invocations.
In Hermes, reload local MCP with /reload-mcp, then verify a subsequent MCP call.
See support and the extrovert-support skill.
