elvanto-mcp

elvanto-mcp

Self-hosted MCP server for Elvanto church management. Enables querying upcoming rosters, service songs, and submitting unavailability via Claude or ChatGPT.

Category
Visit Server

README

elvanto-mcp

Summary

A personal, self-hosted MCP server for your Elvanto account. It runs as a single AWS Lambda behind a free Function URL — nothing is running (or billing) between requests, and continuous deployment ships every push to main straight to AWS.

For a couple of calls a week the AWS cost is effectively $0 (Lambda, DynamoDB and SSM sit comfortably inside the always-free tier; the only guaranteed spend is CloudWatch log storage, i.e. cents per month).

Usage

Once connected, ask Claude things like:

  • "Ask Elvanto when I'm next rostered on at church."
  • "Use the Elvanto tool to check which songs will be played this Sunday."
  • "Check Elvanto to tell me who's rostered on the band this week."
  • "Ask Elvanto for my upcoming roster."
  • "Use the Elvanto MCP to submit an unavailability for 19–23 June, reason: Away."

These map onto five tools:

Tool What it answers
get_my_upcoming_roster "When am I next on?" / "My upcoming roster" — services you're rostered on, soonest first
get_service_songs "Songs this Sunday" — song list per service for a date (defaults to upcoming Sunday)
get_service_roster "Who's on the band this week?" — everyone rostered for a date, filterable by department
list_upcoming_services Upcoming published services
add_unavailability Registers an all-day, non-repeating, all-locations unavailability (see caveat below)

Unavailability caveat: Elvanto's public API documents no unavailability endpoint. The tool attempts unavailabilities/add and, if your account rejects it, responds with a clear message and the exact details to enter manually. Everything else uses documented endpoints (services/getAll, people/search).

"You" are identified automatically as the logged-in Elvanto user (OAuth mode), or by the optional person-email parameter (API-key mode).

Requirements

  • An Elvanto account, plus ONE of two credential modes:
    • Elvanto OAuth (recommended — works without admin access): ask a church admin to register an OAuth application once (Elvanto → Settings → Integrations → API, register application) and give you its Client ID and Client Secret. The app itself grants nothing — access is created only when you later sign in to Elvanto through this server, and every request then runs with your own member permissions.
    • API key (admins only): Elvanto → Settings → Account Settings → API.
  • An AWS account, with the AWS CLI configured locally and the SAM CLI installed (brew install aws-sam-cli).
  • A GitHub repository (for continuous deployment) and a Claude plan that supports custom connectors.

Installation

All commands assume region ap-southeast-2; change it in samconfig.toml and the commands if you prefer another.

Local: run and test

npm ci
npm run check      # typecheck + unit tests
npm test           # tests only
npm run build      # bundle sanity check
sam build          # bundle exactly as production does

The unit tests cover the OAuth flow, the MCP endpoint and the Elvanto response parsing with a stubbed Elvanto API — no credentials needed.

Production: manual deploy

1. Create the setup password parameter (once — secrets never touch git):

aws ssm put-parameter --region ap-southeast-2 --type SecureString \
  --name /elvanto-mcp/oauth-password --value 'A_STRONG_PASSWORD_YOU_CHOOSE'

The setup password is what protects your data during connector approval — pick a strong one.

2. Deploy:

npm ci && npm run check
sam build && sam deploy

The stack output McpEndpoint is your connector URL, e.g. https://abc123.lambda-url.ap-southeast-2.on.aws/mcp.

If your church isn't in Australia/Sydney, deploy with sam deploy --parameter-overrides ChurchTimezone=Pacific/Auckland.

3a. Elvanto credentials — OAuth mode (no admin access needed):

Give your church admin the callback URL https://YOUR_FUNCTION_URL/elvanto/callback and ask them to register an OAuth application with it (Elvanto → Settings → Integrations → API). Then store the credentials they give you:

aws ssm put-parameter --region ap-southeast-2 --type SecureString \
  --name /elvanto-mcp/elvanto-client-id --value 'CLIENT_ID_FROM_ADMIN'

aws ssm put-parameter --region ap-southeast-2 --type SecureString \
  --name /elvanto-mcp/elvanto-client-secret --value 'CLIENT_SECRET_FROM_ADMIN'

Finally, open https://YOUR_FUNCTION_URL/elvanto/connect in a browser, enter your setup password, and sign in to Elvanto once. The server stores your Elvanto session (auto-refreshed from then on) and identifies "you" automatically — no person-email needed.

3b. Elvanto credentials — API-key mode (admins only):

aws ssm put-parameter --region ap-southeast-2 --type SecureString \
  --name /elvanto-mcp/elvanto-api-key --value 'YOUR_ELVANTO_API_KEY'

aws ssm put-parameter --region ap-southeast-2 --type SecureString \
  --name /elvanto-mcp/person-email --value 'you@example.com'

person-email must match the email on your Elvanto profile; it tells the server who "me" is, since an API key is account-wide.

Production: continuous deployment (GitHub Actions + OIDC)

One-time bootstrap of the deploy role — no AWS keys are ever stored in GitHub (replace the repo name):

aws cloudformation deploy --region ap-southeast-2 \
  --template-file infra/github-oidc.yaml \
  --stack-name elvanto-mcp-github-oidc \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameter-overrides GitHubRepo=YOUR_GITHUB_USER/elvanto-mcp

(If your AWS account already has the GitHub OIDC provider, add CreateOidcProvider=false.)

Then in the GitHub repo → Settings → Secrets and variables → Actions → Variables, set:

  • AWS_ROLE_ARN — the DeployRoleArn output of the stack above
  • AWS_REGION — optional, defaults to ap-southeast-2
  • CHURCH_TIMEZONE — optional, e.g. Pacific/Auckland

Every push to main now runs typecheck + tests, then sam deploy (.github/workflows/deploy.yml).

Integration: Claude

  • Claude (web/desktop/mobile): Settings → ConnectorsAdd custom connector, paste the McpEndpoint URL. Claude discovers the OAuth endpoints, registers itself and opens the approval page — enter your setup password. Done.

  • Claude Code:

    claude mcp add --transport http elvanto https://YOUR_URL/mcp
    

Integration: ChatGPT

ChatGPT custom connectors (Settings → Connectors → Advanced → Developer mode) use a different OAuth callback domain, which this server blocks by default. To allow it, redeploy with:

sam deploy --parameter-overrides AllowedRedirectHosts=chatgpt.com,openai.com

then add the McpEndpoint URL as a connector in ChatGPT and approve with the same setup password.

Technical Detail

Claude (custom connector)
   │  OAuth 2.1: dynamic registration + PKCE + password-gated consent
   ▼
Lambda Function URL  ──►  Hono router on Lambda (Node 22, ARM)
   ├── /.well-known/*          OAuth discovery metadata
   ├── /oauth/*                register / authorize / token (Claude-facing)
   ├── /elvanto/connect        one-time Elvanto sign-in (password-gated)
   └── /mcp                    MCP Streamable HTTP (stateless JSON)
                │
                ├── DynamoDB (elvanto-mcp-oauth): hashed tokens, TTL-expired,
                │      plus the server's own Elvanto OAuth session
                ├── SSM SecureString: setup password + Elvanto credentials
                └── api.elvanto.com/v1 (HTTPS; Bearer user token or API key)

AWS services used:

Service Role Cost at this usage
Lambda (ARM, 512 MB) + Function URL The entire server; URL is free, no API Gateway needed Free tier
DynamoDB (on-demand) OAuth clients, codes and hashed tokens, auto-expired via TTL Free tier
SSM Parameter Store (SecureString) Setup password + Elvanto credentials (OAuth client or API key) Free
CloudWatch Logs (30-day retention) Error logs only Cents/month
IAM + GitHub OIDC Keyless continuous deployment Free

Code layout:

Security

  • Your Elvanto credentials never leave AWS. The client secret / API key live in SSM SecureStrings (KMS-encrypted), readable only by the Lambda role, and are never included in MCP responses. Claude only ever sees tool output (roster/song data).
  • OAuth mode is least-privilege by construction: the server acts as you in Elvanto, so it can never see more than your own member account can. The one-time sign-in at /elvanto/connect is password-gated and CSRF-protected with single-use state tokens, so nobody else can bind an Elvanto session to your server.
  • OAuth 2.1 for the connector: dynamic client registration, authorization code + PKCE (S256 required), single-use codes, refresh-token rotation.
  • Consent is password-gated. Registering a client grants nothing; the authorize page requires your setup password, with a lockout after 20 failed attempts per hour.
  • Redirect URIs are restricted to claude.ai / claude.com / anthropic.com (HTTPS only) unless you opt in to more, so tokens can't be sent to arbitrary sites.
  • Tokens are stored as SHA-256 hashes in DynamoDB with TTLs — a leaked table can't be replayed. Expiry is enforced on read, not just by DynamoDB TTL.
  • CD uses GitHub OIDC — no long-lived AWS keys in GitHub, and the deploy role is scoped to this stack's resources and the main branch only.

Troubleshooting

  • "Missing SSM parameter(s)" in tool output → the parameters weren't created in the same region as the stack.
  • "Not connected to Elvanto yet" → OAuth mode needs the one-time sign-in: open /elvanto/connect on the Function URL and log in.
  • "Elvanto session has expired" → repeat the /elvanto/connect sign-in (e.g. after the church admin revoked the app, or a long idle period).
  • No person found → API-key mode: person-email doesn't match your Elvanto profile email.
  • Connector auth loop → remove and re-add the connector; if you redeployed to a fresh stack the DynamoDB table (registered clients) was reset.
  • Logs: sam logs -n McpFunction --stack-name elvanto-mcp --tail (tool inputs/outputs are not logged; only errors are).

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured