{
  "markdown": "# eXcalibur MCP\n\nSword-swift posting of pretty tweets to X (Twitter) via AI agents, monetized\nwith Bitcoin Lightning micropayments through the DPYC(TM) Tollbooth protocol.\n\n[![Version](https://img.shields.io/badge/version-0.34.4-blue)](https://github.com/lonniev/excalibur-mcp)\n[![Python](https://img.shields.io/badge/python-3.12+-green)](https://python.org)\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)\n\neXcalibur is a [FastMCP](https://github.com/jlowin/fastmcp) server that lets\nAI agents post to X (Twitter) with rich Unicode formatting and optional images.\nPatron authentication for X uses OAuth2 Authorization Code + PKCE -- no\ncredentials appear in chat. Operator credentials (X app keys, BTCPay) arrive\nvia human-in-the-loop Secure Courier and are stored in the operator's\nper-operator Neon vault schema. Tool calls are metered with\n[Tollbooth DPYC(TM)](https://github.com/lonniev/tollbooth-dpyc) pre-funded\nLightning balances -- Don't Pester Your Customer.\n\nPart of the [DPYC(TM) Social Contract](https://github.com/lonniev/dpyc-community).\n\n## Getting Started\n\nConnect via Horizon -- no local install needed:\n\n```\nhttps://www.fastmcp.cloud/mcp/lonniev/excalibur-mcp\n```\n\n### Step 1: Check Session\n\nCall `excalibur_session_status` to see your current session state. If you have\nan active session with funded credits, you are ready to post.\n\n### Step 2: X OAuth2 + PKCE (Patron Credentials for X)\n\n1. Get your Nostr npub -- use the dpyc-oracle's `how_to_join()` tool, or any\n   Nostr client.\n2. Call `excalibur_begin_oauth(npub=<npub>)` -- returns an `authorize_url`.\n   Open the primary `authorize_url` (not a shortlink) in your browser and\n   authorize the app on X.\n3. Call `excalibur_check_oauth_status(npub=<npub>)` -- completes the code\n   exchange, vaults the tokens, and activates your session.\n\nSessions are keyed by npub. Tokens are stored in the Neon vault and\nauto-refresh on expiry.\n\n### Step 3: Secure Courier (Operator Credentials)\n\nOperator credentials (X OAuth2 app keys, BTCPay connection) are delivered via\nSecure Courier (`service=\"excalibur-operator\"`). This is a human-in-the-loop\nflow: the operator consciously approves each credential delivery via their\nNostr client. On `receive_credentials`, the relay DM is destructively drained\n-- credentials exist only in the Neon vault after receipt.\n\n## Credits Model\n\nTool calls cost `api_sats` per call. Credits have a `tranche_lifetime` --\neach purchase creates a tranche that expires after the configured lifetime.\nAuth and balance tools are always free. Use `excalibur_check_balance` to see\nyour balance and tranche expiry. Top up via `excalibur_purchase_credits`.\n\n## Available Tools\n\n### Standard Tools (from the wheel)\n\nStandard DPYC(TM) tools are registered by `register_standard_tools()` from\n[tollbooth-dpyc](https://github.com/lonniev/tollbooth-dpyc). These include\nsession management, credit operations, Secure Courier, pricing, notarization,\nand Oracle delegation. Each tool is identified by a deterministic UUID v5.\n\n| Category | Tools |\n|----------|-------|\n| Session & Billing | `session_status`, `check_balance`, `account_statement`, `account_statement_infographic`, `restore_credits`, `service_status`, `check_price`, `check_authority_balance` |\n| Secure Courier | `request_credential_channel`, `receive_credentials`, `forget_credentials` |\n| Npub Proof | `request_npub_proof`, `receive_npub_proof` |\n| Purchase | `purchase_credits`, `check_payment` |\n| Pricing & Constraints | `get_pricing_model`, `set_pricing_model`, `reset_pricing_model`, `list_constraint_types` |\n| Notarization | `notarize_ledger`, `list_notarizations`, `get_notarization_proof` |\n| Onboarding | `get_operator_onboarding_status`, `get_patron_onboarding_status` |\n| OAuth (X) | `begin_oauth`, `check_oauth_status` |\n| Oracle (delegated) | `oracle_about`, `oracle_how_to_join`, `oracle_lookup_member`, `oracle_get_tax_rate`, `oracle_network_advisory` |\n\n### Domain Tools (eXcalibur-specific)\n\nDomain tools are defined in `server.py` (with storage handlers under\n`tools/`). Prices are set by the operator's pricing model -- preview any\ncall with `check_price`. Read and auth tools are free.\n\n**Posting to X**\n\n| Tool | Description |\n|------|-------------|\n| `post_tweet` | Post a text tweet with markdown-to-Unicode rich text formatting |\n| `post_tweet_image` | Post a tweet with a hero banner image (image URL or SVG rendered to PNG) |\n| `get_x_profile` | Fetch the connected X account's handle and name for this patron (free) |\n\n**Stored posts (drafts & scheduling)**\n\n| Tool | Description |\n|------|-------------|\n| `create_post` | Store a new post (draft or scheduled); returns its `post_id` |\n| `get_post` | Read one stored post by id (owner-scoped) |\n| `list_posts` | List your stored posts, server-side sorted, filtered, and paginated |\n| `update_post` | Patch a stored post (`doc`, `publish_at`, `recurrence`, `status`) |\n| `delete_post` | Delete a stored post (soft delete by default) |\n\n**Snippets & Voice**\n\n| Tool | Description |\n|------|-------------|\n| `list_snippets` | List your saved post snippets (server-side sorted/filtered/paginated) |\n| `get_snippet` | Read one saved snippet by id |\n| `save_snippet` | Save a reusable snippet (opening/footer/CTA) |\n| `delete_snippet` | Delete a saved snippet (free, owner-scoped) |\n| `get_voice` | Read your saved writing Voice (profile blurb + banned words) |\n| `save_voice` | Save your per-npub writing Voice |\n\n**AI editorial & dynamic blocks**\n\n| Tool | Description |\n|------|-------------|\n| `refine_post_region` | Refine a flagged region of a post with an LLM, server-side |\n| `resolve_dynamic_block` | Start resolving a dynamic (prompt-backed) post block; returns a claim check |\n| `fetch_dynamic_block` | Redeem a `resolve_dynamic_block` claim check (free, proof-gated) |\n\n**Scheduler**\n\n| Tool | Description |\n|------|-------------|\n| `process_scheduled_posts` | Publish every due scheduled post (operator-only) |\n| `get_scheduler_log` | Read recent scheduler-tick outcomes |\n| `scheduler_status` | The scheduler's configuration and current status (free; any proven patron) |\n| `scheduler_pending` | What the scheduled-post cron Worker is waiting on (operator-only) |\n| `scheduler_check_now` | Run one scheduler tick now (operator-only) |\n\nOAuth flow tools (`begin_oauth`, `check_oauth_status`) are now standard tools\nprovided by the wheel (see the Standard Tools table above).\n\nAll tools that take an `npub` also accept a `proof: str` parameter for\nkind-27235 Schnorr proof attestation.\n\n## Security\n\n- **Npub identity** -- Patrons are identified by a Nostr public key (`npub`),\n  not an email or password. One keypair per role, managed by the user.\n- **Kind-27235 Schnorr proof** -- Tool calls carry an NIP-98-style Schnorr\n  signature proving the caller controls the claimed npub. Proof is cached for\n  ~1 hour; renew via `request_npub_proof` / `receive_npub_proof`.\n- **Human-in-the-loop Secure Courier** -- Credential delivery requires\n  conscious operator approval via Nostr DM. On receipt, the relay message is\n  destructively drained so secrets exist only in the encrypted Neon vault.\n- **Per-operator Neon schema** -- Each operator's credentials are stored in an\n  isolated Postgres schema with a dedicated LOGIN role. No cross-operator\n  access.\n\n## Architecture\n\n```\nsrc/excalibur_mcp/\n  server.py        FastMCP server -- domain tools + register_standard_tools()\n  config.py        Pydantic settings from environment variables\n  oauth_flow.py    X-specific OAuth2 Authorization Code + PKCE wrapper\n  x_client.py      X API v2 client with OAuth 2.0 Bearer token auth\n  formatter.py     Markdown -> Unicode rich text (bold, italic, headers)\n  refine.py        Server-side editorial refinement for the post editor\n  resolve.py       Server-side resolution of dynamic (prompt-backed) post blocks\n  scheduler.py     Scheduled-post firing (publishes due posts on the owner's behalf)\n  tools/           Domain storage handlers (posts, snippets, voices)\n  db/              eXcalibur persistence on the wheel's NeonVault (posts, scheduler runs, migrations)\n```\n\n**Key design choices:**\n\n- **OAuth2 Authorization Code + PKCE** -- Patron tokens acquired via browser\n  flow. `begin_oauth` returns the primary `authorize_url` (not a shortlink);\n  `check_oauth_status` completes the exchange. Tokens stored in Neon vault,\n  auto-refreshed on expiry. No OAuth 1.0a.\n- **UUID v5 tool identity** -- Every tool (standard and domain) has a\n  deterministic UUID derived from its capability name. Pricing, constraints,\n  and billing all key on UUID.\n- **Standard tools from the wheel** -- `register_standard_tools()` provides\n  all DPYC(TM) infrastructure tools. Only domain-specific X/Twitter tools are\n  defined in `server.py`.\n- **Markdown to Unicode** -- Converts `**bold**`, `*italic*`, and `# headers`\n  to Unicode characters that render in tweets without markup syntax.\n\n## Self-Hosting\n\n### Environment Variables\n\n#### DPYC(TM) Identity (required to boot)\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `TOLLBOOTH_NOSTR_OPERATOR_NSEC` | Yes | Operator's Nostr secret key for identity bootstrap and DM encryption |\n\nThis is the only env var required to start. All other secrets (X app keys,\nBTCPay credentials) arrive via Secure Courier credential templates and are\nstored in the per-operator Neon vault schema.\n\n#### Operator Credentials (via Secure Courier)\n\nThese are delivered via Secure Courier (`service=\"excalibur-operator\"`), not\nset as environment variables:\n\n| Credential | Description |\n|------------|-------------|\n| `client_id` | X OAuth2 Client ID (from X Developer Portal) |\n| `client_secret` | X OAuth2 Client Secret (from X Developer Portal) |\n| `btcpay_host` | BTCPay Server URL |\n| `btcpay_store_id` | Store ID for invoices |\n| `btcpay_api_key` | API key with invoice permissions |\n\nCertified operators bootstrap their Neon database URL from the Authority via\nencrypted Nostr DM -- `NEON_DATABASE_URL` is not read from the environment.\n\n#### Optional Tuning\n\n| Variable | Description |\n|----------|-------------|\n| `TOLLBOOTH_NOSTR_RELAYS` | Comma-separated relay URLs (overrides defaults) |\n| `SEED_BALANCE_SATS` | Starter credits for new users (default: 0) |\n| `DPYC_REGISTRY_CACHE_TTL_SECONDS` | How long to cache the DPYC community registry (default: 300) |\n| `CONSTRAINTS_ENABLED` | `\"true\"` to enable constraint engine evaluation on tool calls |\n| `CONSTRAINTS_CONFIG` | Optional constraint-engine configuration payload |\n| `SCHEDULER_WORKER_URL` | Public URL of the scheduled-post cron Worker (default: `https://excalibur-scheduler.lonniev.workers.dev`) |\n\n## Troubleshooting\n\n### Cold start\n\nOn first request after deploy, the service may take a few seconds to\ninitialize Neon connections and load the pricing model. The runtime handles\nthis with inline retry -- the caller receives the result after the warm-up\ncompletes, not a \"service unavailable\" error.\n\n### Credential lifecycle states\n\nCredential-related responses describe the current lifecycle state with clear\nguidance, not error messages:\n\n| State | Meaning | Next step |\n|-------|---------|-----------|\n| `no_session` | No npub recognized | Call `session_status` with your npub |\n| `awaiting_oauth` | OAuth flow started, browser not yet authorized | Open the `authorize_url` and authorize |\n| `awaiting_courier` | Secure Courier channel opened, credentials not yet received | Reply via Nostr client with credentials JSON |\n| `proof_expired` | Npub proof cache expired (~1 hour) | Call `request_npub_proof` then `receive_npub_proof` |\n| `insufficient_balance` | Credits exhausted or tranche expired | Call `purchase_credits` to top up |\n\n### Common tool call situations\n\n- **\"proof is required\"** -- Call `request_npub_proof` then\n  `receive_npub_proof` to prove npub ownership. The cache expires after ~1\n  hour; renew with a fresh request/receive cycle.\n- **\"Insufficient credit balance\"** -- Call `purchase_credits` to top up.\n  Check tranche expiry with `check_balance`.\n- **\"Operator credentials not configured\"** -- Operator setup issue, not the\n  patron's problem.\n\n**Don't Pester Your Customer:** Do NOT ask the patron to re-authenticate,\nre-authorize, or re-do OAuth unless the response specifically says the upstream\ntoken is expired or invalid.\n\n## Development\n\n```bash\n# Install in development mode\ncd excalibur-mcp\npython -m venv .venv\nsource .venv/bin/activate\npip install -e \".[dev]\"\n\n# Run tests\n.venv/bin/pytest tests/\n\n# Type checking\nmypy src/excalibur_mcp/\n\n# Formatting\nblack src/ tests/\nruff check src/ tests/\n```\n\n## DPYC(TM) Ecosystem\n\n- [tollbooth-dpyc](https://github.com/lonniev/tollbooth-dpyc) -- Python SDK for Tollbooth monetization (vault, auth, pricing, Lightning payments, Nostr identity)\n- [dpyc-community](https://github.com/lonniev/dpyc-community) -- Governance registry: membership, advisories, threat model\n- [dpyc-oracle](https://github.com/lonniev/dpyc-oracle) -- Community concierge (free onboarding + member lookup)\n- [tollbooth-authority](https://github.com/lonniev/tollbooth-authority) -- Certification backbone (Schnorr-signed purchase-order certificates)\n- [tollbooth-sample](https://github.com/lonniev/tollbooth-sample) -- Sample Operator (canonical template)\n- [tollbooth-pricing-studio](https://github.com/lonniev/tollbooth-pricing-studio) -- iOS pricing-model editor / operator console\n- [cypher-mcp](https://github.com/lonniev/cypher-mcp) -- Monetized graph answers: named Cypher templates over Neo4j/AuraDB\n- [schwab-mcp](https://github.com/lonniev/schwab-mcp) -- Charles Schwab brokerage data\n- [thebrain-mcp](https://github.com/lonniev/thebrain-mcp) -- TheBrain personal knowledge graph\n- [excalibur-mcp](https://github.com/lonniev/excalibur-mcp) -- X/Twitter posting\n- [taxsort-mcp](https://github.com/lonniev/taxsort-mcp) -- Tax classification + Cloudflare Pages UI\n- [optionality-mcp](https://github.com/lonniev/optionality-mcp) -- Options analytics (brokerage-data operator)\n- [tollbooth-oauth2-collector](https://github.com/lonniev/tollbooth-oauth2-collector) -- OAuth2 callback handler (advocate service)\n- [tollbooth-shortlinks](https://github.com/lonniev/tollbooth-shortlinks) -- URL shortener utility\n\n## Trademarks\n\nDPYC, Tollbooth DPYC, and Don't Pester Your Customer are trademarks of\nLonnie VanZandt. See the\n[TRADEMARKS.md](https://github.com/lonniev/dpyc-community/blob/main/TRADEMARKS.md)\nin the dpyc-community repository for usage guidelines.\n\n## License\n\nApache License 2.0 -- see [LICENSE](LICENSE) for details.\n",
  "bytes": 14562,
  "sha": "ca7c967ee0ca6c57c55210610ca97293e0df1627e50db5ca9d5ccc08cf329d09",
  "repo_slug": "lonniev/excalibur-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_lonniev_excalibur_mcp_8e81a924/readme"
}