Skip to main content
Glama
mcnamee

elvanto-mcp

by mcnamee

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).

Related MCP server: uptime-kuma-mcp-server

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).

Maintenance

ActivityStale
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers