{
  "markdown": "# estevao-mcp\n\n[![npm](https://img.shields.io/npm/v/estevao-mcp)](https://www.npmjs.com/package/estevao-mcp)\n[![MCP Registry](https://img.shields.io/badge/MCP_Registry-io.github.dodopok%2Festevao--mcp-blue)](https://registry.modelcontextprotocol.io/?search=estevao)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)\n\nMCP (Model Context Protocol) server for the [Estêvão API](https://github.com/dodopok/estevao-api) — the liturgical engine behind the Ordo app. Gives Claude and any MCP client accurate Anglican liturgical data: calendar (with real precedence rules), lectionary readings, and the fully assembled Daily Office across multiple editions of the Book of Common Prayer / Livro de Oração Comum.\n\n## Quick start\n\n**Hosted (no key to manage)** — sign in with your Google/e-mail account when the browser opens:\n\n```bash\nclaude mcp add --transport http estevao https://mcp.caminhoanglicano.com.br/mcp   # Claude Code\ncodex mcp add estevao --url https://mcp.caminhoanglicano.com.br/mcp               # Codex CLI\n```\n\nOther clients: see [Connecting any MCP client](#connecting-any-mcp-client).\n\n**Local (stdio)** — you need an Estêvão API key (`estevao_…`):\n\n```bash\nclaude mcp add estevao --env ESTEVAO_API_KEY=estevao_your_key -- npx -y estevao-mcp\n```\n\nOr in `.mcp.json` / Claude Desktop config:\n\n```json\n{\n  \"mcpServers\": {\n    \"estevao\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"estevao-mcp\"],\n      \"env\": { \"ESTEVAO_API_KEY\": \"estevao_your_key\" }\n    }\n  }\n}\n```\n\nThen ask things like *\"what are the readings for next Sunday?\"*, *\"assemble tonight's Compline\"* or *\"compare Christmas in the 1662 and 2019 prayer books\"*.\n\n## Tools\n\nDates accept `YYYY-MM-DD`, `today` or `next-sunday`. Every tool takes an optional `prayer_book` (default `loc_2015`); all tools are read-only. Prayer book codes are validated by the API rather than pinned in this server, so editions added upstream work without a release here — `list_prayer_books` always shows the current catalogue.\n\n| Tool | What it does |\n|---|---|\n| `get_liturgical_day` | Season, color, liturgical year, celebration/saint, collect and readings for a date |\n| `get_calendar_month` | Month grid: color, celebration and week per day |\n| `get_year_overview` | Year structure: seasons, movable feasts and key dates |\n| `get_readings` | Lectionary readings (first, psalm, second, gospel), optionally per service |\n| `get_lectionary_cycle` | Sunday (A/B/C) and weekday (1/2) cycles for a year |\n| `get_daily_office` | The complete Daily Office (morning/midday/evening/compline) as markdown or structured JSON |\n| `search_celebrations` | Full-text search of feasts, saints and holy days |\n| `list_celebrations` | Browse the sanctoral calendar with filters (type, movable, year) |\n| `get_celebration` | One celebration in detail: transfer rules, calculation, collects, readings |\n| `list_prayer_books` | Available prayer books (20+ editions, pt-BR/en/es/cy) and Bible versions |\n| `get_prayer_book_preferences` | Which preferences a book accepts (psalm translation, canticles, cycles) plus its office-level options |\n| `explain_liturgical_day` | The reasoning behind a date: precedence, transfers, colour and how each reading was chosen |\n| `compare_prayer_books` | Side-by-side comparison of 2–4 prayer books for the same day or office |\n\n## Resources & prompts\n\n- **Resources**: `ordo://prayer-books`, `ordo://bible-versions`, `ordo://today`, plus templates `ordo://day/{date}`, `ordo://explain/{date}` (the decision trail), `ordo://office/{date}/{office_type}` (markdown) and `ordo://calendar/{year}/key-dates`.\n- **Prompts** (strictly factual): `build_liturgy_sheet` (print-ready boletim), `explain_feast` (history, precedence, color), `explain_why` (why this reading/saint/colour, answered from the engine's decision trail rather than inferred), `compare_traditions` (side-by-side across editions).\n\n> **Editorial note:** this server intentionally exposes only factual liturgical data and faithful document assembly. It does not (and will not) ship prompts that generate sermons, homilies or devotional reflections.\n\n## Environment variables\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `ESTEVAO_API_KEY` | — (required for stdio) | API key for the Estêvão API |\n| `ESTEVAO_BASE_URL` | `https://api.caminhoanglicano.com.br` | Override for local/staging |\n| `ESTEVAO_DEFAULT_PRAYER_BOOK` | `loc_2015` | Default prayer book code |\n| `ESTEVAO_TIMEZONE` | system | IANA timezone used to resolve `today` |\n| `ESTEVAO_LANGUAGE` | — | Default label language (`pt-BR`, `en`, `es`) and upstream `preferences[language]` |\n\n### Per-book preferences\n\nThe date-scoped tools take a `preferences` object forwarded to the API, which unlocks the\nper-edition options the Ordo app exposes — the Coverdale psalter on the English books\n(`{ \"psalm_translation\": \"coverdale\" }`), monthly vs appointed psalm cycles, canticle and\nopening-sentence choices, family-rite variants. The accepted keys differ per book, so call\n`get_prayer_book_preferences` first rather than guessing.\n\n### Languages\n\nLiturgical content always stays in the prayer book's own language (a 1662 office is English, LOC 2015 is Portuguese). Labels generated by the server — office titles, `Season/Tempo/Tiempo`, comparison headings — automatically follow the book's language, and can be overridden per call (`language` param) or globally (`ESTEVAO_LANGUAGE`).\n\n## Remote server (Streamable HTTP + OAuth)\n\nThe same server runs as a remote MCP endpoint (`POST /mcp`, stateless Streamable HTTP).\nDeployed with OAuth enabled, users connect with a browser sign-in instead of pasting a key:\n\n```bash\nclaude mcp add --transport http estevao https://mcp.caminhoanglicano.com.br/mcp\ncodex mcp add estevao --url https://mcp.caminhoanglicano.com.br/mcp && codex mcp login estevao\n```\n\nThe client discovers the authorization server, registers itself, opens the consent screen,\nand the user signs in with the same Google/e-mail account they use in the\n[developer portal](https://estevao.caminhoanglicano.com.br). The server then provisions an\nEstêvão API key for that account, encrypts it at rest and uses it for every upstream call —\nthe user never sees or handles a key, and the client's token is never forwarded upstream.\n\n### Auth modes\n\nThe mode is chosen by environment, and resolved per request in this order:\n\n1. **Single-key** (personal deployment): `ESTEVAO_API_KEY` set on the server. Optionally set\n   `ESTEVAO_MCP_TOKEN` to require `Authorization: Bearer <token>` from clients.\n2. **API key** (unchanged, for existing integrations): the caller sends its own key in\n   `X-API-Key` (or `Authorization: Bearer estevao_…`). Disable with\n   `ESTEVAO_MCP_ALLOW_API_KEY_HEADER=false`.\n3. **OAuth 2.1** (recommended for public deployments): the caller sends an access token issued\n   by this server. Enabled when the OAuth variables below are all set.\n\n### OAuth configuration\n\n| Variable | Required | Purpose |\n|---|---|---|\n| `MCP_PUBLIC_URL` | yes | Public origin, e.g. `https://mcp.caminhoanglicano.com.br`. Also the OAuth issuer |\n| `MCP_ENCRYPTION_KEY` | yes | 32 bytes (`openssl rand -hex 32`) — encrypts stored API keys |\n| `DEVELOPER_FIREBASE_PROJECT_ID` | yes | Firebase project of the **developer portal** (not the mobile app) |\n| `FIREBASE_API_KEY` / `FIREBASE_AUTH_DOMAIN` | yes | Web config used by the consent screen |\n| `DATABASE_URL` | strongly recommended | Postgres for clients, codes, tokens and the key vault. Without it, state is in memory and lost on restart |\n| `MCP_DATABASE_SSL` | no | Force TLS on the database connection (auto-detected) |\n| `ESTEVAO_PORTAL_URL` | no | Developer portal link shown on the consent screen |\n| `MCP_ALLOW_CLIENT_ID_METADATA_DOCUMENTS` | no | Accept URL-shaped `client_id`s (default `true`) |\n| `MCP_FIREBASE_AUTH_PROXY` | no | Serve Firebase's sign-in helper from this origin (default `true`, see below) |\n| `MCP_TRUST_PROXY` | no | Express `trust proxy` (default `1`, one edge hop). Rate limits key off the real client IP |\n\nSetting only some of these is a configuration error and the server refuses to start, rather\nthan silently falling back to key-only mode. The Postgres schema is created on boot.\n\n#### Same-origin sign-in (required on mobile and Safari)\n\nBy default the Firebase Web SDK runs its sign-in helper on `<project>.firebaseapp.com`, a\ndifferent origin from this server. Safari's ITP (16.1+), Firefox and the in-app browsers used\nby mobile assistants block the cross-origin round trip that flow needs: the user picks a Google\naccount and then nothing happens. So this server reverse-proxies `/__/auth/*` to the Firebase\nhelper and points the consent screen at its own origin, which is the fix\n[Firebase documents](https://firebase.google.com/docs/auth/web/redirect-best-practices) for it.\nSign-in uses a full-page redirect rather than a popup, for the same reason.\n\nTwo one-time console steps make this work:\n\n1. **Firebase Console → Authentication → Settings → Authorized domains**: add the MCP host.\n2. **Google Cloud Console → APIs & Services → Credentials → the Web OAuth 2.0 client** used by\n   Firebase: add `https://<mcp host>/__/auth/handler` to *Authorized redirect URIs*.\n\nSet `MCP_FIREBASE_AUTH_PROXY=false` to go back to the stock cross-origin behaviour.\n\n#### Diagnosing a failed sign-in\n\nSign-in runs in the user's browser, so a failure there reaches no log by itself. The consent\nscreen posts beacons to `/oauth/diagnostics`, which the server writes to stderr:\n\n```\n[consent] rendered request=… client=Claude\n[consent] stage=loaded pending=false storage=true ua=…\n[consent] stage=redirect-start request=… ua=…\n[consent] stage=redirect-lost request=… ua=…     ← came back from Google, state was lost\n[consent] stage=redirect-error code=auth/…       ← Firebase rejected the sign-in\n[consent] approve failed request=… error=…       ← sign-in worked, key provisioning did not\n```\n\nOnly error codes and flags are reported — never tokens or credentials.\n\nEndpoints: `/.well-known/oauth-protected-resource` (also under `/mcp`),\n`/.well-known/oauth-authorization-server`, `/authorize`, `/token`, `/register`, `/revoke`,\nand the consent screen at `/oauth/consent`. `GET /healthz` reports the active mode.\n\nSecurity properties: PKCE `S256` is mandatory, authorization codes are single-use and expire in\n60s, refresh tokens rotate on use, access tokens are opaque and stored only as digests, tokens\nare bound to this server's resource identifier (RFC 8707) and rejected otherwise, and the\nEstêvão API key is AES-256-GCM encrypted at rest.\n\n### Connecting any MCP client\n\nThe endpoint is standard Streamable HTTP with OAuth 2.1 discovery, so any spec-compliant\nclient can connect. Concretely:\n\n| Client | How |\n|---|---|\n| **Claude Code** | `claude mcp add --transport http estevao https://mcp.caminhoanglicano.com.br/mcp` |\n| **Claude Code plugin** | `/plugin marketplace add dodopok/estevao-mcp` then `/plugin install estevao@estevao` |\n| **Claude Desktop / claude.ai** | Settings → Connectors → Add custom connector → paste the URL |\n| **Codex CLI** | `codex mcp add estevao --url …/mcp` then `codex mcp login estevao` |\n| **Gemini CLI** | `~/.gemini/settings.json`: `{\"mcpServers\":{\"estevao\":{\"httpUrl\":\"…/mcp\",\"oauth\":{\"enabled\":true}}}}` |\n| **VS Code / Cursor / Windsurf** | Add an MCP server of type `http` with the URL; the editor runs the OAuth flow |\n| **MCP Inspector** | `npx @modelcontextprotocol/inspector`, transport \"Streamable HTTP\", paste the URL |\n| **Anything else** | `{\"type\":\"http\",\"url\":\"https://mcp.caminhoanglicano.com.br/mcp\"}` |\n\nInteroperability details that make this work across clients:\n\n- **Discovery everywhere clients look.** Protected resource metadata is served both at the root\n  and under `/mcp`; authorization server metadata is served at\n  `/.well-known/oauth-authorization-server`, its `/mcp` path-inserted variant, and both\n  OpenID Connect discovery spellings.\n- **Registration.** Dynamic Client Registration (RFC 7591) *and* Client ID Metadata Documents\n  are both accepted; clients that pre-register a `client_id` work too.\n- **Client authentication.** `none` (public clients, the common case) and both\n  `client_secret_post` and `client_secret_basic` for confidential ones.\n- **Scopes.** There is one read-only scope, `liturgy:read`. Clients that ask for unrelated\n  scopes (`openid profile`, `mcp`, or nothing) still get a working connection.\n- **CORS.** Preflight is answered and `WWW-Authenticate` is exposed, so browser-based clients\n  can read the challenge and start the flow.\n- **Probes.** Unauthenticated `GET`/`DELETE` on `/mcp` return the auth challenge rather than a\n  bare 405, so clients that probe before authenticating still discover the flow.\n\n### Client config for a key-based deployment\n\n```json\n{\n  \"mcpServers\": {\n    \"estevao\": {\n      \"type\": \"http\",\n      \"url\": \"https://<your-host>/mcp\",\n      \"headers\": { \"X-API-Key\": \"estevao_your_key\" }\n    }\n  }\n}\n```\n\nExtra env: `PORT` (default 3333), `ESTEVAO_MCP_ALLOWED_HOSTS` (comma-separated; enables\nDNS-rebinding protection).\n\n## Plugin para Codex, Claude e outros clientes\n\nO repositório inclui um pacote universal em [`plugins/estevao`](plugins/estevao), com manifests\npara Codex e Claude Code e uma configuração MCP compartilhada. O pacote aponta para o servidor\nhospedado, então a conexão usa OAuth e não exige copiar uma API key.\n\nPara Claude Code:\n\n```\n/plugin marketplace add dodopok/estevao-mcp\n/plugin install estevao@estevao\n```\n\nPara Codex CLI:\n\n```bash\ncodex mcp add estevao --url https://mcp.caminhoanglicano.com.br/mcp\ncodex mcp login estevao\n```\n\nVeja o guia visual de integração no [portal do desenvolvedor](https://estevao.caminhoanglicano.com.br/docs/mcp).\nO mesmo endpoint funciona em qualquer cliente MCP compatível com Streamable HTTP e OAuth 2.1.\n\n## Development\n\n```bash\nnpm install\nnpm run typecheck && npm test    # vitest + msw fixtures, no network\nnpm run build                    # tsup → dist/index.js + dist/http.js\nnpm run inspector                # manual testing with the MCP Inspector\n\n# end-to-end against a local estevao-api (docker-compose up in that repo):\nSMOKE_KEY=estevao_… npx tsx scripts/smoke.ts\n\n# remote transport (see .env.example for the OAuth variables):\nnpm run build && node dist/http.js\ndocker build -t estevao-mcp . && docker run -p 3333:3333 --env-file .env estevao-mcp\n```\n\n## Releasing\n\n```bash\nnpm version patch        # bumps package.json AND server.json (version hook), commits + tags\ngit push --follow-tags\nnpm publish --access public\nmcp-publisher publish    # mcp-publisher login github, first time\n```\n\nThe npm `version` lifecycle hook (`scripts/sync-version.ts`) keeps `server.json` in sync and enforces the registry's 100-char description limit. The server's advertised MCP version comes from `package.json` at build time.\n\n`server.json` lists both the hosted endpoint (`remotes`) and the npm package (`packages`), so\nregistry clients can pick either. Keep the hosted URL in step with the actual deployment.\n\nAlternatively, once GitHub Actions is available with the `NPM_TOKEN` secret, `git push --follow-tags` alone triggers the release workflow (npm with provenance + MCP registry via GitHub OIDC).\n\n## License\n\nMIT\n",
  "bytes": 15217,
  "sha": "f1623982120d4ff23f7e24e7d9f5e3ccc277d1f7f0c9f1e9038b9c96d53d37f0",
  "repo_slug": "dodopok/estevao-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_dodopok_estevao_mcp_da8e6895/readme"
}