io.github.lonniev/excalibur-mcp
FastMCP server for posting formatted content to X (Twitter) — Tollbooth-monetized, DPYC-native
Que pouvez-vous faire avec io.github.lonniev/excalibur-mcp ?
proof_required-- The connection does not prove that npub (or its- "Insufficient credit balance" -- Call
purchase_creditsto top up. - "Operator credentials not configured" -- Operator setup issue, not the
Installation
Ajoutez ceci à la configuration MCP de votre client (Claude Desktop : claude_desktop_config.json ; Cursor : .cursor/mcp.json ; VS Code et les autres utilisent la même forme « mcpServers »).
claude mcp add --transport http excalibur-mcp 'https://excalibur-mcp.fastmcp.app/mcp'
{
"mcpServers": {
"excalibur-mcp": {
"type": "http",
"url": "https://excalibur-mcp.fastmcp.app/mcp"
}
}
}
Documentation
Tirée du README du projet sur GitHub, actualisée chaque semaine. Les liens ouvrent GitHub.
Sommaire
eXcalibur 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.
Questions fréquentes
Qu'est-ce que io.github.lonniev/excalibur-mcp ?
FastMCP server for posting formatted content to X (Twitter) — Tollbooth-monetized, DPYC-native
Comment installer io.github.lonniev/excalibur-mcp ?
Il fonctionne comme serveur distant. Dans Claude Code : claude mcp add --transport http excalibur-mcp 'https://excalibur-mcp.fastmcp.app/mcp'. Pour les autres clients, collez la configuration JSON de cette page dans leurs réglages MCP.
Faut-il une clé API pour io.github.lonniev/excalibur-mcp ?
Son entrée au registre n'indique aucune clé ni secret obligatoire. Certaines fonctions peuvent tout de même demander un compte auprès du service auquel il se connecte.
io.github.lonniev/excalibur-mcp est-il open source ?
Son code est public sur GitHub, mais le dépôt ne déclare pas de licence standard.
Qui maintient io.github.lonniev/excalibur-mcp ?
Il est publié par lonniev. AI.tn le référence sans le maintenir ni le recommander.
Issu du registre MCP officiel. Être listé n'est pas une recommandation : vérifiez le code et les permissions d'un serveur avant de lui donner accès à vos données.