MCP for PADI
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
MCP for PADI
MCP for PADI is an MCP server that lets any AI app (Claude, ChatGPT, Codex, VS Code, Cursor and others) read and manage your PADI online dive logbook, the logbook at learning.padi.com/logbook. Ask in your own words: "How many dives did I log this year?", "Log yesterday's boat dive at the house reef, 18 m for 45 minutes", "Which was my deepest dive?".
You can use the hosted service at padi.mcp.visin.ch (MCP address https://padi.mcp.visin.ch/mcp) or run your own copy. The command, the npm package and the Docker image are called padi-mcp.
Unofficial. MCP for PADI is an independent open-source project by Marco Visin. It is not made by, affiliated with, or endorsed by PADI (Professional Association of Diving Instructors). PADI is a registered trademark of PADI Worldwide Corp.
PADI has no public API. This server uses the same private API as the PADI web app, signed in as your own PADI account. PADI can change that API at any time, and then the server stops working until it is updated.
Not for dive planning or safety decisions. The logbook is a record of past dives, and this software and the AI app can make mistakes. Never use it to plan a dive, to work out decompression, gas or no-stop limits, or for any other safety decision. Check what the AI writes into your logbook.
There are two ways to run it:
Web service (multi-user) | Local (single user) | |
For | AI apps that connect to remote MCP servers (Claude, ChatGPT, Codex, VS Code, ...), shared by many people | Claude Desktop, Claude Code, Cursor, VS Code and other local MCP clients |
Start with |
|
|
Sign-in to PADI | Each user connects their PADI account on the account page; only an encrypted refresh token is kept | Environment variables of one person |
AI app sign-in | OAuth: each user creates an account and connects their AI app | None (stdio), or a bearer token ( |
Settings | Web pages for users, plus an admin page | Environment variables |
What the AI can do:
Area | What the AI can do |
Logbook | List and search dives (by date, text, type, status), read every detail of a dive, export dives as CSV or JSON |
Statistics | Number of dives, total bottom time, deepest and longest dive, averages, first and last dive, per year, month, location or dive type |
Logging | Log new dives (recreational and training), change dives, delete dives, search PADI's dive site list |
Verification | Look up a PADI Pro by member number and send a dive to them for verification |
Certifications | List your PADI eCards (title, date, instructor, dive center, card image) |
Settings | Read and set the units of the PADI logbook (metric or imperial) |
Photos | Show the photos attached to a dive |
Contents
Related MCP server: TrainingPeaks MCP Server
Web service for AI apps (multi-user)
In web mode the server is a small website plus an MCP endpoint:
People create an account on the website. On the account page they connect their PADI account: they type the e-mail and password they use on PADI and click Save and test. The server signs in to PADI right away and saves the connection only if that works. The password is used once and never stored: the server keeps only an encrypted PADI refresh token. They also choose the units (the PADI logbook setting, metric or imperial) and their time zone (it decides what "today" is), and can turn on read-only mode.
Once connected, the account page shows the PADI name, the affiliate id, the number of dives and the last sign-in. Units, time zone and read-only can be changed there without signing in again. Reconnect signs in to PADI again (needed after a PADI password change), and Remove deletes the stored sign-in. If PADI ends the sign-in, the page and the AI app both say so, and the user signs in again with Reconnect.
They add one URL (
https://your-server/mcp) to their AI app (in Claude a custom connector). The app opens a sign-in window, they sign in to your server and click Allow. From then on the app works with their PADI logbook.An admin page controls who can sign up (request access with approval, invite codes, open, closed), read-only mode for everyone, which AI apps may connect, token lifetimes, users and connected apps.
Deploy
Claude connects from Anthropic's cloud, so the server must be reachable on the internet over HTTPS on port 443 with a normal certificate and a public IPv4 address.
Docker Compose with automatic HTTPS (recommended). Point a DNS name at your machine, open ports 80 and 443, then:
git clone https://github.com/Mrc527/PADI-MCP.git
cd PADI-MCP/deploy
cp .env.example .env # set DOMAIN, ADMIN_EMAIL, ADMIN_PASSWORD
docker compose up -dCaddy obtains the certificate. Open https://DOMAIN, sign in with the admin account, and you are done. Data (the SQLite database and the encryption key) lives in the data volume: back it up.
Without Docker (Node.js 22.13 or newer):
npm install && npm run build
PUBLIC_URL=https://padi-mcp.example.com TRUST_PROXY=1 DATA_DIR=/var/lib/padi-mcp \
node dist/index.js --web --port 3000Put it behind an HTTPS reverse proxy (see deploy/nginx.conf.example): forward everything to port 3000, keep the Host header, allow at least 300 seconds read timeout and do not strip the WWW-Authenticate header. If you use Cloudflare, do not enable "Block AI bots" or "Bot Fight Mode" for this host: Claude's requests (User-Agent Claude-User, from 160.79.104.0/21 and 153.61.208.0/20) would be blocked.
The server needs outbound HTTPS to:
PADI's hosts (see How sign-in to PADI works).
The hosts of the AI apps' client metadata documents. When an app connects with a
client_idURL, the server fetches that document, for example fromclaude.aiorchatgpt.com. If the fetch fails, built-in copies of Claude's and ChatGPT's documents are used; other apps that sign in this way cannot connect without it.Its own
PUBLIC_URL, for Check public setup on the admin page.Your SMTP server, only if you want e-mails about access requests.
It needs no other outbound access.
If no administrator is configured (both ADMIN_EMAIL and ADMIN_PASSWORD), the first person who signs up becomes the administrator. After that, the server works by approval: other people can request access (with an optional message), and an administrator approves or rejects each request on the admin page. You can switch to invite codes only, open sign-up or closed sign-up there.
Deploy with GitHub Actions (CI/CD)
The workflow .github/workflows/ci.yml tests every pull request and deploys every push to main to your own server over SSH. The default server is nebula.visin.ch, and the hosted service runs there as padi.mcp.visin.ch.
What happens on a push to main:
Test: type check, build, unit tests and integration tests on Node.js 22 and 24. The integration tests run against an in-process mock of the PADI API (
PADI_MCP_IT=1), so CI needs no PADI account and never talks to PADI.Image: build the Docker image for
linux/amd64andlinux/arm64(so it runs on Intel/AMD and on ARM servers such as Oracle Cloud Ampere), start each one and check that it works (health check, sign-in page, OAuth discovery, MCP endpoint). Then publish it toghcr.io/mrc527/padi-mcpwith the tagssha-<commit>andlatest.Deploy: connect to the server over SSH (the server's host key is pinned), copy deploy/server to it and run
deploy.sh, which:pulls the new image by its digest. It uses the job's own token, which can read this repository and its packages and expires when the job ends. The login is kept in a temporary folder and deleted right after.
saves a copy of the database inside the data volume (
/data/backups, the newest 10 are kept),restarts the service with the new image and waits for its health check,
switches back to the last healthy version by itself if the new one does not become healthy.
Public check: wait until
https://DOMAIN/healthzanswers from the internet, and check that the OAuth metadata there belongs to this service.
Pull requests only run steps 1 and 2, and nothing is published. Deploys never run at the same time.
One-time setup
Shortcut for Ubuntu servers: deploy/server/setup-server.sh does steps 1 to 3 and prepares the secrets for step 5. Copy it to the server (for example paste it into nano setup-server.sh), run sudo bash setup-server.sh and answer its questions. It writes the two GitHub secrets to a file only root can read and prints what to do next. You still need step 4 (reverse proxy and DNS) and to add the secrets in GitHub. The steps below explain what it does.
Sharing a server with MCP for iCloud. Both services can run on the same machine (they do on nebula.visin.ch). They share the deploy user, but each has its own folder (~/padi-mcp), its own SSH key for GitHub, its own host name and its own local port. MCP for iCloud listens on 127.0.0.1:3000, so give this service another port, for example HOST_PORT=3001. setup-server.sh offers the next free port by itself.
1. Prepare the server. Install Docker Engine with the Compose plugin (instructions). Then create a user that is only used for deploys (works on Ubuntu and Oracle Linux; skip this if the user exists already):
sudo useradd -m -s /bin/bash deploy
sudo usermod -aG docker deployMembers of the docker group control Docker, so they are as powerful as root on that machine. Keep this user for deploys only.
2. Create an SSH key for GitHub on your computer (without a passphrase):
ssh-keygen -t ed25519 -N "" -C "github-deploy padi-mcp" -f padi-mcp-deploy
cat padi-mcp-deploy.pubOn the server, add that public key for the deploy user, with the word restrict in front of it (it turns off port forwarding and terminals for this key; the deploy does not need them). Paste your own key in place of ssh-ed25519 AAAA...:
sudo -u deploy sh -c 'mkdir -p -m 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'
echo 'restrict ssh-ed25519 AAAA... github-deploy padi-mcp' | sudo -u deploy tee -a /home/deploy/.ssh/authorized_keys3. Create the settings file on the server (with nano, vi or any editor):
sudo -u deploy mkdir -p /home/deploy/padi-mcp
sudo -u deploy nano /home/deploy/padi-mcp/.envStart with this, using your own values:
DOMAIN=padi.mcp.visin.ch
HOST_PORT=3001
ADMIN_EMAIL=you@example.com
ADMIN_PASSWORD='a long password'Set both ADMIN_EMAIL and ADMIN_PASSWORD: the service is online as soon as the first deploy finishes, and without them the first person who signs up becomes the administrator. Put values that contain a $ in single quotes. All other options (MASTER_KEY, TRUST_PROXY, SMTP_URL, ...) are explained in deploy/server/.env.example; each deploy copies that file next to .env and only changes the PADI_MCP_IMAGE line of .env. DOMAIN needs a DNS record pointing to the server, and Claude needs https://DOMAIN on port 443 with a valid certificate.
4. Point your reverse proxy at the service. The service listens on 127.0.0.1:HOST_PORT on the server (3000 by default). Give it its own host name (for example padi.mcp.visin.ch, a DNS record for the same server) and add a site for it to the proxy that already runs there:
nginx: see deploy/nginx.conf.example.
Caddy:
padi.mcp.visin.ch { reverse_proxy 127.0.0.1:3001 }A proxy that runs in Docker (Traefik, Nginx Proxy Manager, ...): inside that container,
127.0.0.1is the container itself. Put both on the same Docker network with acompose.override.ymlnext tocompose.ymlon the server (the pipeline never touches it), and forward tohttp://padi-mcp:3000(inside the Docker network the port is always 3000):services: padi-mcp: networks: [default, proxy] networks: proxy: external: true name: your-proxy-networkTraefik with the Docker provider also needs labels. Use the entrypoint and certificate resolver names of your Traefik (
docker inspectanother routed container to see them):services: padi-mcp: networks: [default, proxy] labels: - traefik.enable=true - traefik.docker.network=your-proxy-network - traefik.http.routers.padi-mcp.rule=Host(`padi.mcp.visin.ch`) - traefik.http.routers.padi-mcp.entrypoints=websecure - traefik.http.routers.padi-mcp.tls.certresolver=myresolver - traefik.http.services.padi-mcp.loadbalancer.server.port=3000 networks: proxy: external: true name: your-proxy-networkAfter creating or changing the file, run
docker compose up -din that folder as the deploy user.No proxy on the server: add
COMPOSE_PROFILES=caddyto.env. The bundled Caddy then answers on ports 80 and 443 and gets a Let's Encrypt certificate by itself. Only one service on a machine can do this: if MCP for iCloud uses its bundled Caddy already, use a shared proxy instead.
5. Add the secrets in GitHub.
Secret | Value |
| The content of the private key file |
| The output of |
Check the host key before you save it: ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub on the server must show the same fingerprint as ssh-keyscan -t ed25519 nebula.visin.ch | ssh-keygen -lf - on your computer. After saving the secrets you can delete the private key file from your computer.
Where to put them depends on your GitHub plan and on whether the repository is private:
GitHub Pro, Team or Enterprise (or a public repository): open Settings > Environments > New environment and name it
production. Under Deployment branches and tags choose Selected branches and tags and addmain. Add the two secrets as environment secrets there. Then only workflows running onmaincan use the key.GitHub Free with a private repository: environments are not available. Add the two secrets as repository secrets under Settings > Secrets and variables > Actions. The workflow reads them the same way, but any branch of this repository could then use the key, so only give write access to people you trust. (Pull requests from forks never get secrets.)
Approving each deploy by hand (Required reviewers) is only offered for public repositories and on GitHub Enterprise.
Optional variables (Settings > Secrets and variables > Actions > Variables):
Variable | Default | Description |
|
| SSH host name of the server. |
|
| SSH user. |
|
| SSH port. For another port, create |
|
| Folder on the server, relative to the user's home folder or absolute. |
The server must accept SSH from GitHub's runners (their addresses change, so the port has to be reachable from the internet; on Oracle Cloud check the security list).
6. Deploy from main. Deploys only run from the main branch. If the repository has no main branch yet, create it from your working branch and make it the default branch, so Run workflow picks it by default:
git push origin <your-branch>:mainThen set Settings > General > Default branch to main. From now on every push or merge to main is deployed; follow it under Actions > CI/CD. You can also start a deploy by hand with Actions > CI/CD > Run workflow (choose main under Use workflow from). If the settings file on the server is missing, the first run copies the files there and stops with a message that says what to do.
When it is green, open https://DOMAIN, sign in with ADMIN_EMAIL and ADMIN_PASSWORD, connect your PADI account on the account page, and add https://DOMAIN/mcp to your AI app.
Day to day
Run the server commands as the deploy user, in its folder: sudo -iu deploy, then cd padi-mcp. Do not run deploy.sh as root; it stops with a message if you try.
Roll back: Actions > CI/CD > Run workflow, choose
mainandrollback. Or on the server:./deploy.sh rollback. This goes back one version; a second rollback does nothing. To deploy an older version, open its run in the Actions list and re-run its Deploy job (GitHub allows this for 30 days), or revert the change onmainand push.See what runs:
./deploy.sh statusshows the running image, the previous one and the last deploys.docker compose logs -ffollows the logs. The full output of each deploy is in.deploy/deploy.log.Change settings: edit
.env, then rundocker compose up -d(or deploy again).Back up: deploys keep database copies inside the Docker volume, on the same disk. To keep a copy somewhere else, run
./deploy.sh backup, then copy the backups and the encryption key out of the volume and off the server, for example:docker compose cp padi-mcp:/data/backups ./backups docker compose cp padi-mcp:/data/master.key ./backups/and download the
backupsfolder (for example withscp). Keep it safe: it contains the encryption key. Skipmaster.keyif you setMASTER_KEYin.env(then back up.env).
MCP Registry
The service is listed in the official MCP Registry as io.github.Mrc527/mcp-for-padi, so AI apps and MCP directories can find it. The listing is server.json: name, description, icons and the address https://padi.mcp.visin.ch/mcp.
To publish or update it: Actions > MCP Registry > Run workflow on the default branch (or push a tag v<version>). The workflow signs in to the registry with GitHub OIDC (no secret needed), takes the version from package.json, validates server.json and publishes it. Published versions cannot be changed, so bump version in package.json before publishing again; the run stops early if that version is already in the registry. The live entry: https://registry.modelcontextprotocol.io/v0.1/servers/io.github.Mrc527%2Fmcp-for-padi/versions/latest.
Connect an AI app
The account page has a Connect your AI app section with one-click links and ready-to-copy commands. Every app uses the same address, https://your-server/mcp, and signs in with OAuth (the app opens your server, the user signs in and clicks Allow; no password or key goes into the app).
App | How |
Claude (web, desktop, mobile) | Add to Claude button (it fills in the name "MCP for PADI" and the address), or Customize > Connectors > Add custom connector. Keep the defaults ("Use Claude's published identity"). Team and Enterprise owners add it once under Organization settings > Connectors. |
ChatGPT | On chatgpt.com: Plugins > + > Add custom MCP server, name "MCP for PADI", authentication OAuth. Availability depends on the plan and workspace settings. |
Claude Code |
|
Codex (CLI, IDE, ChatGPT desktop app) |
|
VS Code (GitHub Copilot) | Install in VS Code button ( |
Visual Studio | Install in Visual Studio button ( |
GitHub Copilot CLI |
|
Gemini, Le Chat, Perplexity | Steps on the account page (custom app / custom MCP connector, OAuth). |
Cursor, Kiro, LM Studio, Goose | One-click install links on the account page. |
Gemini CLI, Devin (Windsurf), Cline, Jan |
|
Zed, JetBrains, Warp, Raycast, Msty, Open WebUI, LibreChat, n8n | Settings snippets and steps on the account page. |
Other apps | Add a remote (Streamable HTTP) MCP server with the address. Apps that only start local programs, or cannot sign in to remote servers (Roo Code, Continue), can use |
Users can see and revoke connected apps, and a log of recent tool calls, under Connected apps.
Start page, languages and logo
The start page (
/) explains the service before sign-in: what it can do, example requests, how to connect, privacy and security, and an FAQ. Signed-in people go straight to their account.Every page is available in English, Italian, German, French, Spanish and Simplified Chinese. The language follows the browser (
Accept-Language); the menu at the bottom of every page switches it and remembers the choice in a cookie (?lang=deworks on any page). Texts live insrc/web/i18n/<language>/<area>.ts; English is the source and the fallback. A test renders every page and fails if any visible text bypasses the catalogs. The product name, MCP for PADI (common.appName), is the same in every language.The privacy policy (
/privacy) and the terms of use (/terms) are linked from the footer of every page, and/.well-known/security.txtsays where to report security problems (SECURITY.md). They describe the hosted service, run by Marco Visin, Lugano, Switzerland. If you run your own copy, change the name, contact address and date insrc/web/operator.ts, and the place in the legal texts (src/web/i18n/<language>/legal.ts).The logo is our own artwork (an open logbook with a wave and bubbles), not PADI's:
assets/brand/logo.svg, andlogo-small.svgfor 16-32 px.node scripts/make-icons.mjsturns it into the favicon, the app icons, the icons the MCP server sends to AI apps (src/web/brand-assets.ts) and the 512 px directory icon (docs/directory/icon-512.png). It needs Playwright with Chromium; with a global install, runNODE_PATH="$(npm root -g)" node scripts/make-icons.mjs.There is no explainer video in this version. The start page leaves that section out.
Admin page
/admin is available to administrators:
Overview with the MCP address, user counts (also how many users have connected PADI), and a Check public setup button that fetches the discovery documents from the public URL the way Claude does.
Settings: who can create an account (request access with approval, which is the default; invite only; anyone; nobody), allowed sign-up email domains, read-only for everyone, which AI apps may connect (for example only
claude.ai claude.com), dynamic client registration and client metadata documents, token and session lifetimes, default time zone (for users who did not choose their own on the account page).Access requests: approve or reject people who asked for an account, one by one or all at once. The menu shows how many are waiting. If the operator set
SMTP_URL, each administrator also gets an e-mail about new requests at their sign-in address. Requests that come close together arrive in one e-mail (the first about a minute after a request, then at most one every 10 minutes). WithoutSMTP_URL, no e-mail is sent and the admin page still lists the requests. Email addresses are not verified, so anyone can type any address: before you approve, check with the person in another way that the request is theirs. People are not notified automatically, so let them know when you approve them. Unanswered requests are deleted after 30 days, at most 100 can wait at the same time, and one IP address can send 5 requests a day.Invites: create single-use invite codes with an expiry. In approval mode a valid code skips the approval.
Demo account for app reviewers: create a demo account with sample dives (the password is shown once), see the demo accounts and reset their data.
Users: the column "PADI" shows whether each user has connected PADI, has not, or must sign in to PADI again. Make or remove administrators, disable or enable accounts (disabling also disconnects their apps), reset passwords, delete accounts, turn a user into a demo account and back. The last administrator cannot be removed.
OAuth clients: see which AI apps registered and remove them.
Administrators never see PADI passwords (they are not stored) or the users' dives.
Demo account
App reviewers (for example for the Claude Connectors Directory) should not need a real PADI account. A demo account has a PADI logbook simulated inside the server instead:
Create one on the admin page under Demo account for app reviewers: type an email address for signing in (it is only the sign-in name; the server never sends mail to it) and copy the generated password, which is shown once. The account is approved and is never an administrator. You can also turn an existing user (not an administrator) into a demo account and back; their saved PADI connection is deleted when they become one.
Sample data: the diver is "Alex Demo" (alex.demo@example.com) with an invented PADI affiliate id, in the time zone Europe/Zurich. The logbook has 25 invented dives over about three years: an Open Water course (training dives 1 to 4), two Advanced adventure dives, holidays on invented reefs, wrecks and walls, and local lake and quarry dives. Most dives are published, the training dives and a few others are verified by an invented PADI Pro, one dive waits for verification (Pending, so it is locked) and the newest one is a Draft. There are three invented eCards (Open Water, Advanced Open Water, Enriched Air), an invented dive site list and two invented PADI Pros for the instructor lookup. The demo dives have no photos. Dates are relative to the day of the reset, so the data always looks current. Every name, number and place is invented.
The same code as a real account:
LogbookServiceand every tool run unchanged. Only the PADI backend is replaced by an in-memory one (src/demo/logbook.ts).Nothing leaves the server. A demo account never opens a network connection: not to PADI, and not to the operator's
PADI_*_URLoverrides. Submitting a dive for verification works in the tools, but no PADI Pro gets a request.RAM only, reset every day. Every change is kept in memory only. All demo accounts go back to fresh sample data every day at 04:00 Swiss time (Europe/Zurich) and whenever the server restarts. Reset demo data now on the admin page does it at once.
The account page shows a notice instead of the PADI form, and keeps the read-only switch, the connected apps, the activity log and the security page. The administrator's read-only setting for everyone applies to demo accounts too.
Web service configuration
Environment variables for --web:
Variable | Default | Description |
|
| The public HTTPS address of the server, e.g. |
|
| Where the server listens ( |
|
| Folder for the SQLite database ( |
| generated | 32-byte key (64 hex or 44 base64 characters) that encrypts the stored PADI refresh tokens. If unset, one is created in |
| (off) | Set to |
| Create this administrator account on first start ( | |
| (none) | Optional SMTP server for e-mails to administrators about access requests, for example |
| see text | Sender address of those e-mails: an address, or a name and an address ( |
|
| Which PADI logbook endpoint every user's connection uses: |
| PADI | Operator-only overrides that apply to every user, for tests against a local mock PADI API. Users can never change server addresses. |
Everything else is configured on the admin page.
How sign-in to this server works (OAuth)
The server is its own OAuth 2.1 authorization server, built to the MCP authorization specification and Claude's connector requirements:
Discovery: unauthenticated MCP requests get
401withWWW-Authenticate: Bearer ... resource_metadata="..."; protected resource metadata is served at/.well-known/oauth-protected-resource(and/mcp), authorization server metadata at/.well-known/oauth-authorization-server.Clients identify themselves with a Client ID Metadata Document (Claude's published identity) or dynamic client registration. Documents are fetched with SSRF protection, and built-in copies of Claude's and ChatGPT's documents are used if claude.ai or chatgpt.com cannot be reached.
Authorization code flow with PKCE (S256), a consent page that judges an app by where it returns to, never by the name it gives itself (well-known AI apps such as Claude and ChatGPT are shown as recognized by their exact redirect address; answers to localhost, an app's own scheme or VS Code stay on the user's device; any other address gets a warning, and Allow only works after the user ticks a box to confirm they trust it), single-use codes, tokens bound to the MCP URL (RFC 8707), rotating refresh tokens with reuse detection, revocation. The consent page says what the app can do with the logbook: read dives and, unless the account is read-only, add, change and delete dives and send dives to a PADI Pro.
Tokens, sessions and invite codes are stored only as SHA-256 hashes; PADI refresh tokens are encrypted with AES-256-GCM.
Rate limits are applied per OAuth client and per user (Claude's requests for all users come from the same few IP ranges).
How sign-in to PADI works and what is stored
PADI has no public API, and no way for other apps to ask for access. This server signs in the same way the PADI web app does, as your own PADI account, and only ever reads and changes your own logbook. The details are in docs/padi-api.md.
Sign-in. Your PADI e-mail (lowercased) and password are sent once to PADI's sign-in service:
cognito(the first choice): AWS Cognito (cognito-idp.us-west-2.amazonaws.com), the user directory behind PADI accounts, with the public app client of the PADI website.padi: PADI's own sign-in API (api.global-prod.padi.com/auth/api), the one the account.padi.com sign-in page uses.auto(default):cognitofirst. Only if Cognito fails for another reason than a wrong e-mail or password, trypadi.
After a wrong e-mail or password the server never tries again by itself, so your PADI account is not locked. If PADI asks for anything else (a new password, a code, a confirmed e-mail), the server stops and tells you what to do on padi.com.
Tokens. A successful sign-in gives three tokens. The ID token is sent with every logbook request and is valid for one hour. The server renews it with the refresh token two minutes before it expires, and once more when PADI answers "not signed in". PADI decides how long the refresh token stays valid; it ends, for example, when you change your PADI password. Then the server asks you to sign in again. Your PADI affiliate id (the key of your logbook) is always read from your own token, never from what the AI app sends.
Logbook API (PADI_API_MODE):
proxy(default):logbook.global-prod.padi.com/api/Logbook, the logbook endpoint the PADI web app used for years.direct:divelog.global-prod.padi.com/v1/graphql, the endpoint the current PADI web app uses.auto: start withproxy; if it keeps failing (not found or server errors twice in a row, or "not signed in" right after a fresh sign-in), switch todirectuntil the server restarts.
Other PADI hosts: ecard.global-prod.padi.com (eCards), logbook-images-api.padi.com (dive photos), and the instructor lookup on the logbook host (dive-log-service.global-prod.padi.com in direct mode). The PADI client talks to no other hosts. Writes are sent one after the other per user, failed requests are retried at most 3 times with a pause (on 429 and 5xx answers), and every request times out after 20 seconds.
What is stored.
Web service | Local mode | |
PADI password | Never. Used once to sign in, then forgotten. | Only where you put it (environment variable or file). |
PADI refresh token | Encrypted with AES-256-GCM (key in | In memory, or in the file |
Other PADI details | Your PADI e-mail, name, affiliate id, login method, units, read-only setting and time zone, and when you connected (all encrypted with the token). | Nothing. |
Dives, notes, photos, eCards | Never stored. They pass between PADI and your AI app while a request runs. | Never stored. |
Activity | A log of tool calls (time, app, tool, duration, success; no content), kept 90 days. | Nothing. |
Locked dives. Dives that wait for a PADI Pro (Pending) or are verified (Verified, QR Code Verified) are locked: the tools refuse to change or delete them, the same way the PADI web app does.
Local mode (single user)
Check your PADI sign-in. You need the e-mail and password you use on learning.padi.com. If you sign in to PADI with Google, Apple or Facebook, first set a PADI password: use "Forgot password" on account.padi.com with the same e-mail.
Build the server (Node.js 22.13 or newer):
git clone https://github.com/Mrc527/PADI-MCP.git cd PADI-MCP npm install npm run buildTest the connection:
PADI_EMAIL=you@example.com \ PADI_PASSWORD='your PADI password' \ node dist/index.js --checkIt signs in to PADI, counts your dives and prints the result (never a token). It exits with code 0 when everything works.
Add it to your AI client (see Client setup).
To avoid a password in your client settings, set PADI_TOKEN_CACHE to a file once: the first start signs in with the password and writes the refresh token there (mode 0600). Later starts use the file and need no password until PADI ends the sign-in. You can also give a refresh token directly with PADI_REFRESH_TOKEN.
Client setup (local mode)
Replace the path and the values with your own. Keep your PADI password out of files you share.
Claude Desktop
Edit claude_desktop_config.json (Settings > Developer > Edit Config):
{
"mcpServers": {
"padi": {
"command": "node",
"args": ["/absolute/path/to/PADI-MCP/dist/index.js"],
"env": {
"PADI_EMAIL": "you@example.com",
"PADI_PASSWORD": "your PADI password",
"PADI_TOKEN_CACHE": "/absolute/path/to/padi-token.json",
"PADI_TIMEZONE": "Europe/Zurich"
}
}
}
}Claude Code
claude mcp add padi \
-e PADI_EMAIL=you@example.com \
-e "PADI_PASSWORD=your PADI password" \
-e PADI_TOKEN_CACHE=$HOME/.config/padi-mcp/token.json \
-- node /absolute/path/to/PADI-MCP/dist/index.jsCursor
Add this to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):
{
"mcpServers": {
"padi": {
"command": "node",
"args": ["/absolute/path/to/PADI-MCP/dist/index.js"],
"env": { "PADI_EMAIL": "you@example.com", "PADI_PASSWORD": "your PADI password" }
}
}
}VS Code
Add this to .vscode/mcp.json (or run MCP: Add Server and choose Command (stdio)). The top-level key is servers, and VS Code can ask for the password instead of storing it:
{
"inputs": [{ "type": "promptString", "id": "padi-password", "description": "PADI password", "password": true }],
"servers": {
"padi": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/PADI-MCP/dist/index.js"],
"env": { "PADI_EMAIL": "you@example.com", "PADI_PASSWORD": "${input:padi-password}" }
}
}
}Windsurf and other stdio clients use the same command, args and env as the Claude Desktop example.
Without cloning (npx from GitHub)
{
"command": "npx",
"args": ["-y", "github:Mrc527/PADI-MCP"],
"env": { "PADI_EMAIL": "you@example.com", "PADI_PASSWORD": "your PADI password" }
}Single-user HTTP mode
For a personal remote setup without accounts (clients that accept a static bearer token), start the server in HTTP mode. For Claude on the web and other hosted AI apps, use the web service instead.
MCP_HTTP_TOKEN="$(openssl rand -hex 32)" node dist/index.js --http --port 3000The endpoint is http://127.0.0.1:3000/mcp (Streamable HTTP, stateless) and clients must send Authorization: Bearer <token>. By default the server only listens on 127.0.0.1. To expose it, put it behind an HTTPS reverse proxy and set MCP_HTTP_HOST=0.0.0.0 and MCP_HTTP_ALLOWED_HOSTS=mcp.example.com; the server refuses to listen on a public interface without MCP_HTTP_TOKEN. GET /healthz answers {"status":"ok"}.
Configuration reference (local mode)
All settings are environment variables. You need PADI_EMAIL with PADI_PASSWORD, or a refresh token. (In web mode users set the equivalent options on their account page.)
Variable | Default | Description |
| The e-mail you sign in to PADI with. | |
| Your PADI password. | |
| Instead of the password: a PADI refresh token. | |
| (none) | File where the current refresh token is kept between runs (mode 0600). Unset: memory only. The cache wins over |
|
| Units of answers and inputs: |
|
|
|
| system zone | IANA time zone of the server's "today", for example |
|
|
|
|
|
|
| PADI | Other PADI hosts and client, for tests against a local mock only. |
|
|
|
|
| HTTP listen address ( |
| Bearer token required on every HTTP request. | |
| Extra accepted | |
| Browser origins allowed to call the server (CORS). |
See .env.example for a template. The server does not read a .env file by itself: set the values in your MCP client configuration (the env block) or in your shell. To keep them in a file, start the server with Node's --env-file option, for example node --env-file=.env dist/index.js --check. In an MCP client configuration, put "--env-file=/absolute/path/to/.env" before the path to dist/index.js in args.
Tools
The AI sees these tools (write tools are hidden in read-only mode). Every tool has a description that lists the accepted values and units, strictly typed parameters (unknown parameter names are rejected), and is marked as read-only, changing, destructive or reaching other people, so clients such as Claude can ask for confirmation first.
Tool | Kind | What it does |
| read | Name, e-mail, PADI affiliate id, units, read-only, API mode, demo flag, server version and today's date. |
| read | Dives, newest first: |
| read | Every detail of one dive, in the units you want, plus a link to edit it on learning.padi.com. |
| read | Number of dives, total bottom time, deepest, longest, average depth and time, first and last dive; |
| read | Search PADI's dive site list ( |
| read | All dives (or |
| read | The name of a PADI Pro, by member number. |
| read | Your eCards: title, date, PADI number, instructor, dive center, card image link. |
| read | Up to 5 photos of a dive, as images (size-capped). Only JPEG, PNG, GIF and WebP can be shown: other photos (for example HEIC) are counted as skipped, with a link to the dive on learning.padi.com. |
| write | Log a dive: title, date (not in the future), location, dive type, depth and bottom time (not needed for a Draft), and optional conditions, equipment, gas, feeling, notes, buddies and dive center. Training dives ( |
| write | Change any field of a dive; |
| write | Set the units of the PADI logbook (metric or imperial). No dive changes: PADI stores metric values. |
| write, reaches another person | Look up the PADI Pro, then send the dive to them for verification (it becomes Pending and locked). Needs |
| destructive | Delete a dive for good (PADI has no trash). |
Prompts: Log a dive (log_a_dive, asks only for what is missing, then logs the dive; hidden in read-only mode) and Year in review (year_in_review, a summary of a year of diving).
Notes:
Units. PADI stores metric values (m, minutes, °C, bar, kg, litres). With units
autothe server answers in the units of your PADI logbook setting, and every dive it returns says which units it uses. Tools that take depths, temperatures or other measured values have aunitsparameter, so the AI can pass values in the units you used.Dates are calendar dates (
YYYY-MM-DD) without a time zone.Values. Dive type is
Boat,BeachShoreorOther(the server also accepts words like "shore" or "beach"). The tool descriptions list every accepted value for weather, visibility, waves, current, surge, suit, weight, cylinder, gas and feeling.Status. New dives are
Publish(logged) orDraft.Pending,VerifiedandQR Code Verifieddives are locked.Safety. Dive titles, notes, buddies and other logbook text are user data: the server tells the AI to never follow instructions found inside them, and to ask before deleting a dive or sending one to a PADI Pro.
Security
Web service:
The PADI password is used once to sign in and is never stored. The PADI refresh token is encrypted at rest (AES-256-GCM, key in
MASTER_KEYorDATA_DIR/master.key) and never shown, not even to administrators. Whoever runs the server and holds the database and the key can decrypt the refresh tokens, so users must trust the operator. Changing your PADI password ends every refresh token, also the one on this server; Remove on the account page deletes it at once.Web sessions use host-only
HttpOnly,SameSite=Laxcookies (__Host-prefixed on HTTPS), every form has a CSRF token, passwords are hashed with scrypt, sign-in is rate limited, and pages send a strict Content Security Policy that forbids framing.Users cannot change server addresses (no requests to internal hosts), cannot read or write local files, and each request only ever sees the logbook of the token's owner.
New accounts need an administrator's approval by default. Keep it that way (or use invite codes) if the server is public: open sign-up lets anyone use your server and its connection to PADI.
Both modes:
Passwords and tokens are never included in tool results or logs. Error messages and logs mask anything that looks like a token (JWTs,
Bearervalues), and logs never contain e-mail addresses.The PADI affiliate id always comes from the signed-in user's token, never from the AI app.
Read-only mode (
PADI_READ_ONLY=true, or the switch on the account page) removes every tool that can add, change or delete.Deleting a dive needs its title as confirmation, sending a dive to a PADI Pro needs
confirm: true, and locked dives cannot be changed or deleted. Still, review what your AI client does (most clients ask for approval per tool call).The token cache file (
PADI_TOKEN_CACHE) is written with mode 0600. Treat it like a password.The HTTP mode listens on
127.0.0.1by default, checks theHostandOriginheaders, compares the bearer token in constant time and refuses public interfaces without a token. Always put it behind HTTPS when exposing it.
How to report a security problem: SECURITY.md.
Troubleshooting
Run node dist/index.js --check first (local mode), or Save and test on the account page (web service): both sign in to PADI and print a clear error.
Problem | Fix |
"Wrong e-mail or password" | Use the e-mail and password you use on learning.padi.com. Sign in there once in a browser to be sure. The server does not try again by itself, so your account is not locked; wait a moment before you retry. |
You sign in to PADI with Google, Apple or Facebook | Those sign-ins only work in a browser. Set a PADI password first: "Forgot password" on account.padi.com with the same e-mail, then use that password here. Your Google, Apple or Facebook sign-in keeps working. |
"Your PADI sign-in expired" | PADI ended the sign-in, for example after a password change, or the refresh token got too old. Web service: sign in to PADI again on the account page (Reconnect). Local mode: delete the |
PADI asks for a new password, a code or a confirmed e-mail | Sign in once at learning.padi.com in a browser and do what PADI asks, then try again. |
Errors after PADI changed its website or API (for example "not found", server errors, or "not signed in" right after a fresh sign-in) | Try |
A dive "is locked" and cannot be changed or deleted | The dive waits for a PADI Pro (Pending) or is verified. PADI does not let you change it, and neither does this server. |
The AI app uses the wrong date for "today" or "yesterday" | Web service: choose your time zone on the account page. Local mode: set |
Depths or temperatures look wrong | Ask for the units you want, or set |
Claude cannot connect to the web service | Check |
Development
npm install
npm run build # compile to dist/
npm run dev # run from source with tsx
npm run typecheck
npm test # unit tests (integration tests are skipped)The unit tests use a mocked fetch for the PADI client (sign-in success, failure and challenges, refresh, single-flight refresh, retry on 401 and invalid-jwt, the auto API mode switch, query text and headers in proxy and direct mode), and cover the logbook service (array literals, dates, units, merge on update, missing child rows, locked dives, delete confirmation), the MCP tool surface (tool list, annotations, read-only mode, strict arguments, redaction), the demo logbook, and the web service (sign-up, sign-in, CSRF, account settings, the full OAuth flow with the SDK's OAuth client as Claude would run it, token refresh and reuse detection, client metadata documents, admin controls, security headers, translations).
End-to-end tests start the real server (in process, and the built dist/index.js over stdio, --http and --web) against an in-process mock of the PADI API (test/helpers/mock-padi.ts: Cognito, the PADI auth API, the logbook GraphQL API and the REST endpoints). They never talk to PADI and need no PADI account:
npm run build
PADI_MCP_IT=1 npm run test:integrationTest data is always invented: never put real names, PADI numbers or e-mail addresses from real accounts into code, tests or docs.
Project layout:
src/
index.ts CLI entry point (stdio / --web / --http / --check)
config.ts environment configuration
server.ts MCP server, instructions, prompts, service wiring
http.ts Streamable HTTP transport with auth and Host/Origin checks
tooling.ts tool registration helpers (errors, read-only gating, annotations, redaction)
padi/ PADI client: endpoints, sign-in, token session, HTTP, GraphQL, API
logbook/ the contract (types.ts), enums, units, mapping, LogbookService, MCP tools
demo/ the in-memory demo logbook and its invented sample dives
util/ dates, text helpers, errors
web/ multi-user web service: SQLite store, encryption, OAuth provider,
start, account and admin pages, per-user MCP endpoint and connection pool
web/i18n/ page texts per language (en is the source)
assets/brand/ the logo (SVG); scripts/make-icons.mjs makes the icons from it
deploy/ Docker Compose with Caddy, nginx example
deploy/server/ production stack and deploy script used by the CI/CD pipeline
docs/ design notes, the PADI API notes, Connectors Directory texts
scripts/ build helper, Docker image smoke test, icon generator
.github/ CI/CD and MCP Registry workflows, SSH deploy scriptLicense
MIT. MCP for PADI is not made by, affiliated with, or endorsed by PADI. PADI is a registered trademark of PADI Worldwide Corp.
This server cannot be deployed
Maintenance
Related MCP Connectors
Manage sailing logs, trips, boats, sharing, attachments, and account entitlements.
Your own cycling data for your AI assistant: rides, power, climbs and routes. Read-only.
Multi-tenant SaaS for dive, watersports, and outdoor activity operators. Manage trips, sessions, rosters, and bookings — tenant-isolated, OAuth 2.1 secured. Trip, roster, client and session tools for dive and watersports operators, scoped to one tenant. RidgeHQAPP MCP connects AI assistants to a multi-tenant booking platform for dive and watersports operators. Create/manage sessions, trips, rosters, and clients with tenant-scoped OAuth 2.1 or PAT auth — no destructive tool is ever exposed; writes require explicit confirmation.
Query your Logspot analytics, companies, and revenue from any AI assistant, and run your actions.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to read and write MyFitnessPal data, including food diary, exercises, body measurements, nutrition goals, and water intake.MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to interact with TrainingPeaks accounts to analyze training data, manage workouts, update zones, and more using plain English.3-
- AlicenseAqualityDmaintenanceEnables AI assistants to interact with MyFitnessPal data including food diary, exercises, body measurements, nutrition goals, and water intake through natural language.2071MIT
- AlicenseNot gradedqualityBmaintenanceEnables reading and analyzing Garmin Connect data—activities, health metrics, FIT files, and challenges—through natural language, for AI assistants like Claude and ChatGPT.MIT