npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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

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; @next is opt-in preview. Extrovert also operates https://mcp.extrovert.dev/mcp as a stateless Streamable HTTP endpoint with browser OAuth. The same package installs extrovert-mcp for transports and extrovert for 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_KEY for 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/mcp

The 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 hermes

For 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 --json

Refresh 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 whoami

Never 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 --json

The 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.com

Status 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 8787

Run it without building during development:

pnpm run dev            # tsx watch, stdio
pnpm run dev -- --http  # tsx watch, HTTP

Configuration (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 with sign_up and receive a short-lived limited key in-session. The key expires with its activation reservation and is revoked when verify_signup returns its replacement. For an incoming_email response, ask the human to email the reserved inbox, call check_activation with wait_seconds: 55 for a bounded watch, then verify_signup without an OTP once proven. correct_activation_email requires the current revision and a fresh matching email; it does not extend expiry. Previously issued OTPs remain supported until their original expiry. Set EXTROVERT_MOCK=1 to use deterministic in-memory fixtures; create_inbox, send_email, and wait_for_email then 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 claude

Offline: omit EXTROVERT_API_KEY and set EXTROVERT_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 inbox argument takes the canonical opaque inbox_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 fixed org_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 is null deletes it), pass a top-level null to clear all metadata, or omit metadata entirely 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 /mcp and DELETE /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 logic

The 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 --http

MIT (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.