About excalibur-mcp
excalibur-mcp is a FastMCP server for AI agents that publish formatted text or image posts to X, with tools for drafts, scheduling, and conversation leads. It uses OAuth2 PKCE for X authorization and meters tool calls with Bitcoin Lightning micropayments.
Key features
- 01Publish Unicode-formatted text and images
- 02Save, edit, and schedule posts
- 03Manage reusable snippets and writing voice
- 04Find conversations and track potential leads
- 05Review post metrics and performance
Use cases
- 01Publish Markdown content converted for X
- 02Prepare drafts and schedule their publication
- 03Find questions to answer and follow up on threads
MCP support
This FastMCP server exposes tools for X posting, post management, conversation leads, and metrics to MCP clients. Calls are metered against Bitcoin Lightning balances through Tollbooth DPYC.
Configuration
Remote servers need no local install. Pick your client, then copy the config or install in one click.
https://excalibur-mcp.fastmcp.app/mcpclaude mcp add --transport http lonniev-excalibur https://excalibur-mcp.fastmcp.app/mcp{"mcpServers":{"lonniev-excalibur":{"type":"http","url":"https://excalibur-mcp.fastmcp.app/mcp"}}}Open in Cursor{"servers":{"lonniev-excalibur":{"type":"http","url":"https://excalibur-mcp.fastmcp.app/mcp"}}}Open in VS Code{"mcpServers":{"lonniev-excalibur":{"type":"http","url":"https://excalibur-mcp.fastmcp.app/mcp"}}}{"mcpServers":{"lonniev-excalibur":{"serverUrl":"https://excalibur-mcp.fastmcp.app/mcp"}}}Run commands in a terminal; paste JSON into the client’s MCP config and restart. Servers that need sign-in will prompt on first connect.
Tools
87| Parameter | Type | Required | Description |
|---|---|---|---|
| npub | string | Yes | The Nostr public key (npub1...) whose balance to check. |
| dpop_token | string | Yes | Proof that the caller holds npub: the grant envelope from receive_npub_proof, or the raw JSON of a kind-27235 Nostr event signed by npub whose `u` tag is this tool's name. |
Details
From the project READMEeXcalibur MCP
Sword-swift posting of pretty tweets to X (Twitter) via AI agents, monetized with Bitcoin Lightning micropayments through the DPYC(TM) Tollbooth protocol.
eXcalibur is a FastMCP server that lets AI agents post to X (Twitter) with rich Unicode formatting and optional images. Patron authentication for X uses OAuth2 Authorization Code + PKCE -- no credentials appear in chat. Operator credentials (X app keys, BTCPay) arrive via human-in-the-loop Secure Courier and are stored in the operator's per-operator Neon vault schema. Tool calls are metered with Tollbooth DPYC(TM) pre-funded Lightning balances -- Don't Pester Your Customer.
Part of the DPYC(TM) Social Contract.
Getting Started
Connect via Horizon -- no local install needed:
https://excalibur-mcp.fastmcp.app/mcp
The web editor at https://excalibur.tollbooth-dpyc.com drives the same tools from a browser.
Step 1: Check Session
Call excalibur_session_status to see your current session state. If you have
an active session with funded credits, you are ready to post.
Step 2: Prove Your Npub on the Connection
Identity rides on the connection, not on tool arguments. Connecting from an AI
assistant is an OAuth holder connection: call
excalibur_request_npub_proof(patron_npub=<npub>), reply to the Nostr DM it
sends, then call excalibur_receive_npub_proof(patron_npub=<npub>, nonce=<nonce>)
once. The connection then holds your npub for the lifetime you chose in the
reply (up to 30 days), and every tool call passes only npub. A caller that
holds the nsec may instead sign each HTTP request (NIP-98).
Step 3: X OAuth2 + PKCE (Patron Credentials for X)
- Get your Nostr npub -- use the dpyc-oracle's
how_to_join()tool, or any Nostr client. - Call
excalibur_begin_oauth(npub=<npub>)-- returns anauthorize_url. Open the primaryauthorize_url(not a shortlink) in your browser and authorize the app on X. - Call
excalibur_check_oauth_status(npub=<npub>)-- completes the code exchange, vaults the tokens, and activates your session.
Sessions are keyed by npub. Tokens are stored in the Neon vault and auto-refresh on expiry.
Step 4: Secure Courier (Operator Credentials)
Operator credentials (X OAuth2 app keys, BTCPay connection) are delivered via
Secure Courier (service="excalibur-operator"). This is a human-in-the-loop
flow: the operator consciously approves each credential delivery via their
Nostr client. On receive_credentials, the relay DM is destructively drained
-- credentials exist only in the Neon vault after receipt.
Credits Model
Tool calls cost api_sats per call. Each purchase creates a tranche; it
expires only if the operator's pricing model sets a tranche_lifetime.
Auth and balance tools are always free. Use excalibur_check_balance to see
your balance and tranche expiry. Top up via excalibur_purchase_credits.
Available Tools
Standard Tools (from the wheel)
Standard DPYC(TM) tools are registered by register_standard_tools() from
tollbooth-dpyc. These include
session management, credit operations, Secure Courier, pricing, notarization,
and Oracle delegation. Each tool is identified by a deterministic UUID v5.
| Category | Tools |
|---|---|
| Session & Billing | session_status, check_balance, account_statement, account_statement_infographic, restore_credits, service_status, check_price, check_authority_balance |
| Secure Courier | request_credential_channel, receive_credentials, forget_credentials, update_operator_credential, delete_operator_credential, update_patron_credential, delete_patron_credential, get_patron_credential_fields |
| Npub Proof | request_npub_proof, receive_npub_proof, check_proof_status, held_identities |
| Purchase & Coupons | purchase_credits, check_payment, mint_coupon, list_coupons, update_coupon, delete_coupon, redeem_coupon, list_my_coupons, forget_coupon |
| Pricing & Constraints | get_pricing_model, set_pricing_model, reset_pricing_model, list_constraint_types, list_canonical_identities |
| Notarization | notarize_ledger, list_notarizations, get_notarization_proof |
| Onboarding & Adoption | get_operator_onboarding_status, get_patron_onboarding_status, request_adoption, adoption_status, restore_neon_schema |
| Nostr & Diagnostics | get_nostr_profile, publish_nostr_profile, request_echo, report_issue |
| OAuth (X) | begin_oauth, check_oauth_status |
| Oracle (delegated) | oracle_about, oracle_how_to_join, oracle_lookup_member, oracle_get_tax_rate, oracle_network_advisory |
Domain Tools (eXcalibur-specific)
Domain tools are defined in server.py (with storage handlers under
tools/). Prices are set by the operator's pricing model -- preview any
call with check_price. Read and auth tools are free.
Posting to X
| Tool | Description |
|---|---|
post_tweet | Post a text tweet with markdown-to-Unicode rich text formatting |
post_tweet_image | Post a tweet with a hero banner image (image URL or SVG rendered to PNG) |
get_x_profile | Fetch the connected X account's handle and name for this patron (free) |
post_nostr_message | Publish a public Nostr note (kind 1) for your proven npub, signed by an ephemeral scribe key that p-tags you |
Stored posts (drafts & scheduling)
| Tool | Description |
|---|---|
create_post | Store a new post (draft or scheduled); returns its post_id |
get_post | Read one stored post by id (owner-scoped) |
list_posts | List your stored posts, server-side sorted, filtered, and paginated |
update_post | Patch a stored post (doc, publish_at, recurrence, status) |
delete_post | Delete a stored post (soft delete by default) |
Snippets & Voice
| Tool | Description |
|---|---|
list_snippets | List your saved post snippets (server-side sorted/filtered/paginated) |
get_snippet | Read one saved snippet by id |
save_snippet | Save a reusable snippet (opening/footer/CTA) |
delete_snippet | Delete a saved snippet (free, owner-scoped) |
get_voice | Read your saved writing Voice (profile blurb + banned words) |
save_voice | Save your per-npub writing Voice |
AI editorial & dynamic blocks
| Tool | Description |
|---|---|
refine_post_region | Refine a flagged region of a post with an LLM, server-side |
resolve_dynamic_block | Start resolving a dynamic (prompt-backed) post block; returns a claim check |
fetch_dynamic_block | Redeem a resolve_dynamic_block claim check (free, proof-gated) |
Conversation leads
| Tool | Description |
|---|---|
find_conversations | Search X for people asking questions you could answer; score and store them as leads |
list_conversations | Page through your stored leads (free) |
reply_to_conversation | Post your reply into a lead's thread on X and mark the lead engaged |
set_conversation_status | Mark a lead new, seen, engaged or dismissed (free) |
list_conversation_queries | List your saved conversation queries (free) |
save_conversation_query | Create or update a saved conversation query (free) |
delete_conversation_query | Delete a saved query; its leads are kept (free) |
Post metrics
| Tool | Description |
|---|---|
harvest_metrics | Cadence-aware sweep that snapshots X metrics for sent posts (operator-only) |
get_post_metrics | Read the metrics snapshot series for one of your posts |
post_performance | Derived reach scores across your harvested post metrics |
post_performance_infographic | SVG infographic of post performance |
Scheduler
| Tool | Description |
|---|---|
process_scheduled_posts | Publish every due scheduled post (operator-only) |
get_scheduler_log | Read recent scheduler-tick outcomes |
scheduler_status | The scheduler's configuration and current status (free; any proven patron) |
scheduler_pending | What the scheduled-post cron Worker is waiting on (operator-only) |
scheduler_check_now | Run one scheduler tick now (any proven patron) |
scheduler_reissue | Send the scheduler's proof request again, now (operator-only) |
OAuth flow tools (begin_oauth, check_oauth_status) are now standard tools
provided by the wheel (see the Standard Tools table above).
Tools that take an npub take only the npub: the connection proves it (see
Security).
Security
- Npub identity -- Patrons are identified by a Nostr public key (
npub), not an email or password. One keypair per role, managed by the user. - Identity on the connection -- A connection proves its npub in one of
two ways: each HTTP request is signed with the npub's key (NIP-98, kind
27235), or an OAuth holder connection carries an Operator-signed grant
earned through
request_npub_proof/receive_npub_proof. The patron chooses the grant's lifetime, up to 30 days;held_identitiesandcheck_proof_statusshow what a connection holds. - Human-in-the-loop Secure Courier -- Credential delivery requires conscious operator approval via Nostr DM. On receipt, the relay message is destructively drained so secrets exist only in the encrypted Neon vault.
- Per-operator Neon schema -- Each operator's credentials are stored in an isolated Postgres schema with a dedicated LOGIN role. No cross-operator access.
Architecture
src/excalibur_mcp/
server.py FastMCP server -- domain tools + register_standard_tools()
config.py Pydantic settings from environment variables
oauth_flow.py X-specific OAuth2 Authorization Code + PKCE wrapper
x_client.py X API v2 client with OAuth 2.0 Bearer token auth
formatter.py Markdown -> Unicode rich text (bold, italic, headers)
refine.py Server-side editorial refinement for the post editor
resolve.py Server-side resolution of dynamic (prompt-backed) post blocks
resolver.py Builds one post's finished body (the slow half: dynamic blocks)
publisher.py Carries a finished body to X (the fast half: bill, post, record)
scheduler.py Scheduled-post firing (publishes due posts on the owner's behalf)
conversation_scoring.py Pure scoring + query composition for conversation leads
metrics_harvest.py Post-metrics harvest cadence and derived reach scores
nostr_note.py Public Nostr notes signed by an ephemeral scribe key
tools/ Domain storage handlers (posts, snippets, voices, conversations, metrics)
db/ eXcalibur persistence on the wheel's NeonVault (posts, snippets, voices,
conversations, metrics, scheduler runs, migrations)
modal_app.py Detached runner for long dynamic-block resolves (Modal)
scheduler-worker/ Cloudflare Worker cron that fires due scheduled posts
frontend/ The web editor (excalibur.tollbooth-dpyc.com)
Key design choices:
- OAuth2 Authorization Code + PKCE -- Patron tokens acquired via browser
flow.
begin_oauthreturns the primaryauthorize_url(not a shortlink);check_oauth_statuscompletes the exchange. Tokens stored in Neon vault, auto-refreshed on expiry. No OAuth 1.0a. - UUID v5 tool identity -- Every tool (standard and domain) has a deterministic UUID derived from its capability name. Pricing, constraints, and billing all key on UUID.
- Standard tools from the wheel --
register_standard_tools()provides all DPYC(TM) infrastructure tools. Only domain-specific X/Twitter tools are defined inserver.py. - Markdown to Unicode -- Converts
**bold**,*italic*, and# headersto Unicode characters that render in tweets without markup syntax.
Self-Hosting
Environment Variables
DPYC(TM) Identity (required to boot)
| Variable | Required | Description |
|---|---|---|
TOLLBOOTH_NOSTR_OPERATOR_NSEC | Yes | Operator's Nostr secret key for identity bootstrap and DM encryption |
This is the only env var required to start. All other secrets (X app keys, BTCPay credentials) arrive via Secure Courier credential templates and are stored in the per-operator Neon vault schema.
Operator Credentials (via Secure Courier)
These are delivered via Secure Courier (service="excalibur-operator"), not
set as environment variables:
| Credential | Description |
|---|---|
client_id | X OAuth2 Client ID (from X Developer Portal) |
client_secret | X OAuth2 Client Secret (from X Developer Portal) |
btcpay_host | BTCPay Server URL |
btcpay_store_id | Store ID for invoices |
btcpay_api_key | API key with invoice permissions |
llm_api_key | Optional. Model-router key (OpenRouter by default) for refine_post_region and dynamic blocks; posting works without it |
modal_token_id, modal_token_secret, modal_app_name | Optional. Modal workspace that runs long dynamic-block resolves detached from the MCP host |
github_repo, github_token | Optional. Where report_issue files patron field reports (token needs only issues:write) |
Certified operators bootstrap their Neon database URL from the Authority via
encrypted Nostr DM -- NEON_DATABASE_URL is not read from the environment.
Optional Tuning
| Variable | Description |
|---|---|
SCHEDULER_WORKER_URL | Public URL of the scheduled-post cron Worker (default: https://excalibur-scheduler.lonniev.workers.dev) |
RESOLVE_BLOCK_BUDGET_MAX_S | Ceiling, in seconds, on one dynamic block's LLM run (default: 1800); the job, lease and runner timeouts are derived from it |
Troubleshooting
Cold start
On first request after deploy, the service may take a few seconds to initialize Neon connections and load the pricing model. The runtime handles this with inline retry -- the caller receives the result after the warm-up completes, not a "service unavailable" error.
Common tool call situations
Responses describe the current lifecycle state with guidance on the next step, not bare error messages.
proof_required-- The connection does not prove that npub (or its grant expired). Callrequest_npub_proof, reply to the DM, thenreceive_npub_proofonce.check_proof_statusshows how long a grant has left.- "Insufficient credit balance" -- Call
purchase_creditsto top up. Check tranche expiry withcheck_balance. - "Operator credentials not configured" -- Operator setup issue, not the patron's problem.
Don't Pester Your Customer: Do NOT ask the patron to re-authenticate, re-authorize, or re-do OAuth unless the response specifically says the upstream token is expired or invalid.
Development
# Install in development mode
cd excalibur-mcp
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
# Run tests
.venv/bin/pytest tests/
# Type checking
mypy src/excalibur_mcp/
# Formatting
black src/ tests/
ruff check src/ tests/
DPYC(TM) Ecosystem
- tollbooth-dpyc -- Python SDK for Tollbooth monetization (vault, auth, pricing, Lightning payments, Nostr identity)
- dpyc-community -- Governance registry: membership, advisories, threat model
- dpyc-oracle -- Community concierge (free onboarding + member lookup)
- tollbooth-authority -- Certification backbone (Schnorr-signed purchase-order certificates)
- tollbooth-sample -- Sample Operator (canonical template)
- tollbooth-pricing-studio -- iOS pricing-model editor / operator console
- cypher-mcp -- Monetized graph answers: named Cypher templates over Neo4j/AuraDB
- schwab-mcp -- Charles Schwab brokerage data
- thebrain-mcp -- TheBrain personal knowledge graph
- excalibur-mcp -- X/Twitter posting
- taxsort-mcp -- Tax classification + Cloudflare Pages UI
- optionality-mcp -- Options-trading practice game
- goodearth-mcp -- Region-scoped climate analytics for small farms
- beesknees-mcp -- The Bee's Knees race game
- roastify-mcp -- Roastify coffee catalog and packaging artwork
- ChartRemotely-mcp -- Puts a security on a desktop chart remotely
- tollbooth-web -- Shared browser SDK for operator front ends
- tollbooth-oauth2-collector -- OAuth2 callback handler (advocate service)
- tollbooth-shortlinks -- URL shortener utility
Trademarks
DPYC, Tollbooth DPYC, and Don't Pester Your Customer are trademarks of Lonnie VanZandt. See the TRADEMARKS.md in the dpyc-community repository for usage guidelines.
License
Apache License 2.0 -- see LICENSE for details.
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.