jb_gateway_mcp
This MCP server acts as a credential-holding, policy-gated gateway, giving AI agents controlled access to Google services (Gmail, Calendar, Drive) and bank account data (via Enable Banking) without exposing sensitive credentials.
Ping:
pingconfirms the server is reachable; always available.Gmail:
list_messages: List messages for an account, optionally filtered by query.read_message: Get a single message's details (subject, from, snippet, body).send_message: Send an email (requires explicit policy grant; deny-by-default).
Calendar:
list_events: List upcoming events (default primary calendar).create_event: Create an event with start/end times (requires explicit grant).
Drive:
list_files: List files matching a query.read_file: Read metadata and text content of a file.
Bank (read-only, via Enable Banking):
list_accounts: List linked accounts (masked IBANs).get_balance: Retrieve balance for an account.summarize_spending: Aggregated spending totals over a date range.list_transactions_summary: Transactions with date/amount/currency only.list_transactions_detailed: Adds counterparty names and descriptions (requires explicit grant).
Key characteristics: deny-by-default policy gates every tool call per caller-specific grants; credentials and secrets are never exposed; bank data is strictly read-only with privacy protections like IBAN masking and minimal transaction details.
Provides tools for listing, reading, and sending emails through the Gmail API.
Provides tools for listing and reading files through the Google Drive API.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@jb_gateway_mcpWhat's on my calendar for today?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
jb_gateway_mcp
A local MCP server that acts as a credential-holding gateway to Google APIs (Gmail, Calendar, Drive) and, via Enable Banking, read-only bank account data (DNB, Nordea, Revolut, ...). AI agents call MCP tools; the server holds every credential and decides — via a deny-by-default policy — what each caller is allowed to do. Agents never see a token, password, API key, or raw account number.
Full design/architecture: DESIGN.md.
Skills & plugins (recommended if you're using Claude Code)
This repo ships one project skill directly, in .claude/skills/:
run-jb-gateway-mcp— launches the server and drives a real MCP session against it end-to-end (handshake, tool discovery,ping, policy-gated tool calls, audit log integrity check). Ask e.g. "run jb_gateway_mcp" or "smoke-test the gateway." This one stays here since it's a server-maintainer concern, not something plugin users need.
The account-connection and finance-report skills — plus domain subagents
— are distributed separately as Claude Code plugins, so they can be
installed standalone without cloning this repo:
jb_claude_pluggins
(jb-finance-mcp-plugin: bank onboarding + finance reports;
jb-google-notify-plugin: Google onboarding + report notifications via
Gmail). Both depend on this server being installed standalone first — see
"Standalone install" below — and walk through app/consent registration,
running the onboarding CLI, and adding the right policy.yaml grants,
verifying the result against live data before calling it done.
The rest of this README is the manual reference for each step: useful if you're not driving this through Claude Code, or want to understand exactly what the skills automate.
Related MCP server: gg-mcp
Standalone install (for plugin users, or any MCP client)
If you just want the server itself on PATH — e.g. to use the plugins
above, or to point a non-Claude-Code MCP client at it without cloning this
repo — install it as a tool instead of uv sync-ing a clone:
uv tool install --python 3.13 git+https://github.com/jitheshb83/jb_gateway_mcp.gitThis puts jb-gateway-mcp, onboard-google, onboard-bank, and
uninstall-google on your PATH. Also create a policy file — the server
won't start without one, even to serve ping:
mkdir -p ~/.jb_gateway_mcp
echo 'callers: {}' > ~/.jb_gateway_mcp/policy.yamlThat's a valid, safe, deny-everything starting point (see §4 below for the
shape once you're ready to add grants) — ~/.jb_gateway_mcp/policy.yaml
is the default JB_GATEWAY_POLICY_FILE path a standalone install resolves
to automatically, unlike the dev-clone path below where it's the repo's
own tracked policy.yaml.
The rest of this README (steps 1–7) still applies for OAuth client setup,
onboarding, and policy.yaml grants — those are one-time, per-account
steps independent of how the server itself got installed — with one
difference: steps 3 and 7b
below show uv run onboard-google ... / uv run onboard-bank ... run
from inside a cloned repo (the uv sync dev path). With a standalone
install, drop both the cd and the uv run prefix — just
onboard-google ... / onboard-bank ... directly, since they're already
on PATH. If you're developing on this repo itself, uv sync + uv run
(as in "Install" below) is the right mode instead.
Already onboarded accounts via a dev-clone install? You don't need to
re-run onboarding for the standalone path — tokens/sessions live in the OS
keychain (keyring), not tied to which install method wrote them. Only
policy.yaml is per-install (each has its own default location), so the
one thing a switch between install modes always needs is its own grants.
Prerequisites
Python 3.13 (managed automatically by
uv)For Google tools: a Google account you're willing to grant read-only (or send/write) API access to, and a Google Cloud project to create OAuth credentials in
For bank tools: a free Enable Banking account and the bank(s) you want to connect (DNB, Nordea, Revolut, ... currently — see §7)
1. Install
cd jb_gateway_mcp
uv sync2. Create a Google OAuth client (one-time, in Google Cloud Console)
The gateway needs its own OAuth client to run the consent flow. You do this once, in your own Google account — nothing here can do it for you:
Go to Google Cloud Console and create (or pick) a project.
APIs & Services → Library — enable the Gmail API, Google Calendar API, and Google Drive API.
APIs & Services → OAuth consent screen — configure it (External is fine for personal use; add your own account as a test user if the app stays in "Testing" mode).
APIs & Services → Credentials → Create Credentials → OAuth client ID — Application type: Desktop app. Download the resulting JSON — this is your
client_secret.json.Keep this file out of the repo. Store it somewhere outside the project (e.g.
~/.secrets/jb_gateway_mcp/client_secret.json). The.gitignorehere already blocksclient_secret*.jsonas a backstop, but don't rely on that — don't put it in the repo directory at all.
3. Onboard a Google account
This is a one-time, human-run step per Google account. It opens a browser for you to log in and grant consent; the resulting token is written to your OS keychain — it never touches disk in plaintext and is never visible to any agent.
uv run onboard-google \
--account you@example.com \
--client-secrets ~/.secrets/jb_gateway_mcp/client_secret.jsonBy default this requests read-only scopes (Gmail, Calendar, Drive). To
also allow sending mail or creating events, pass --scopes explicitly:
uv run onboard-google \
--account you@example.com \
--client-secrets ~/.secrets/jb_gateway_mcp/client_secret.json \
--scopes \
https://www.googleapis.com/auth/gmail.readonly \
https://www.googleapis.com/auth/gmail.send \
https://www.googleapis.com/auth/calendar \
https://www.googleapis.com/auth/drive.readonlyOn success it prints the account and granted scopes — never a token value. Re-run this any time a refresh token is revoked (the server will raise a clear re-consent error if that happens mid-use).
4. Grant policy access
The server ships with policy.yaml denying everything by default — no
caller can use any tool until you explicitly grant it. Edit policy.yaml:
callers:
local:
allow:
- tool: gmail.list_messages
scope: gmail.readonly
- tool: gmail.read_message
scope: gmail.readonly
- tool: calendar.list_events
scope: calendar.readonly
- tool: drive.list_files
scope: drive.readonly
- tool: drive.read_file
scope: drive.readonly
# Only add these if you actually want an agent to be able to send
# mail / create events on your behalf:
# - tool: gmail.send_message
# scope: gmail.send
# - tool: calendar.create_event
# scope: calendar.eventslocal is the default caller identity for v1 (single-user, local stdio
deployment — see DESIGN.md §7). Override it with the
JB_GATEWAY_CALLER_ID environment variable if you want distinct policies
per launching client (see §6 below).
5. Run it standalone (quick manual test)
The server is started via scripts/start.sh — a thin
wrapper that resolves the project root, checks uv and policy.yaml are
present, and execs into uv run jb-gateway-mcp (so a launching client's
process management/signals reach the real server directly, no wrapper
process left in between). This is the same command every client config
below points at.
./scripts/start.shThis blocks, speaking MCP over stdio — it's meant to be launched by a client, not run interactively. To sanity-check it without a full client, run the automated test suite instead:
uv run pytest -q # 115 tests: unit + a real stdio round-trip test
uv run ruff check .
uv run mypy .The stdio round-trip test in tests/test_server.py spawns the real server
process and calls ping over a real MCP session — the same mechanism any
client uses.
For a fuller live check (handshake, all 13 tools discovered, ping, policy
enforcement on the Google tools, and an audit-log integrity check), run the
project skill's smoke test:
uv run python .claude/skills/run-jb-gateway-mcp/scripts/smoke_test.py6. Connect a real client
Every client config below launches scripts/start.sh
with JB_GATEWAY_CALLER_ID=local — the same caller id already granted
read-only Gmail/Calendar/Drive access in policy.yaml and verified working
end-to-end. This repo is a single-user, local deployment (see
DESIGN.md §7), so every client sharing one caller id is
intentional, not a shortcut — they all run as you, on your machine. If you
later want per-client policies (e.g. a stricter grant set for one client),
give it its own JB_GATEWAY_CALLER_ID and add a matching entry under
callers: in policy.yaml — until you do, any caller id with no entry
there is denied everything by default.
Claude Desktop
Copy config/claude_desktop_config.example.json
into your Claude Desktop config
(~/Library/Application Support/Claude/claude_desktop_config.json on
macOS), replacing the placeholder path with this repo's absolute path, then
restart Claude Desktop.
Claude Code
A ready-to-use .mcp.json already exists at this repo's root
(project-scoped — Claude Code picks it up automatically when you open this
folder). If you'd rather register it globally instead, claude mcp add is
the CLI route — run claude mcp --help to confirm the exact current flags
for your installed version.
Any other MCP client (Cursor, Windsurf, Cline, etc.)
Most MCP clients use the same mcpServers JSON shape. See
config/mcp_client_generic.example.json
and that client's own docs for where its config file lives.
7. Connect a bank account (DNB, Nordea, Revolut, ...)
Independent of the Google setup above and §6 — do this before, after, or
without ever doing them; it's a separate provider with its own app
registration and onboarding CLI. bank.* tools are backed by Enable
Banking, a licensed AISP aggregator (direct bank PSD2 APIs require being a
regulated TPP with an eIDAS certificate — not viable for a personal
project).
7a. Register an Enable Banking application (one-time, in their Control Panel)
Sign in at enablebanking.com/sign-in/ (email + magic link — no business registration needed).
Control Panel → API applications → Add a new application.
Name: anything identifiable.
Redirect URL: exactly
https://localhost:8080/callback— Enable Banking requireshttps://, with no plain-http localhost exception (unlike Google).Privacy/Terms URL: required fields, but not validated while the app stays in Restricted mode (own-accounts-only, which is what this project uses) — any placeholder URL works.
Let the browser generate the private key rather than supplying your own — it downloads once as
<application-id>.pemand never leaves your machine.
Keep the
.pemout of the repo — e.g.~/.secrets/jb_gateway_mcp/enablebanking/<application-id>.pem, same convention asclient_secret.json.Activate the application. A freshly registered app starts "Inactive" and returns
403 Forbiddenon every API call until you click "Activate by linking accounts" in the Control Panel and complete one bank login through their hosted UI. Do this once per institution you plan to connect (DNB, Nordea, Revolut, ...) — Restricted mode only ever serves accounts that have gone through this linking step.
7b. Onboard each institution
uv run onboard-bank --institution dnb \
--application-id <uuid> \
--private-key ~/.secrets/jb_gateway_mcp/enablebanking/<uuid>.pem--application-id/--private-key are only needed the first time — every
institution after that reuses the stored app credential:
uv run onboard-bank --institution nordea
uv run onboard-bank --institution revolutThis is interactive: it opens a bank login URL in your browser, and after
you complete BankID/SCA login, the browser fails to load the final
redirect page (https://localhost:8080/callback?...) — that's expected,
nothing is listening there. Copy the full URL from the address bar and
paste it back at the terminal prompt; the CLI extracts the authorization
code from it. On success it prints e.g. dnb onboarded: 1 account(s) linked, consent valid until 2026-10-30 — never a secret value.
Consent is SCA-backed and valid for 90 days; re-run the same command for the same institution to refresh it — there's no separate "refresh" command, and no way to extend a session without a fresh login (PSD2 requires it).
Supported institution aliases (see
src/jb_gateway_mcp/cli/onboard_bank.py):
dnb, nordea, revolut — all currently Norway (NO). Adding a new one is
a two-line code change.
7c. Grant policy access
callers:
local:
allow:
- tool: bank.list_accounts
scope: bank.readonly
- tool: bank.get_balance
scope: bank.readonly
- tool: bank.summarize_spending
scope: bank.readonly
- tool: bank.list_transactions_summary
scope: bank.readonly
# Adds counterparty name + payment description to transaction results
# (IBANs stay masked either way). Off by default:
# - tool: bank.list_transactions_detailed
# scope: bank.transactions.detailedTiered by design: the default read-only tools never return counterparty
names, payment descriptions, or raw IBANs (every IBAN — the account
holder's own, and any counterparty's — is masked to its last 4 digits).
bank.list_transactions_detailed is the only tool that adds
counterparty/description text, and it needs its own explicit grant.
7d. Verify
The connect-bank-account skill's status-check script (in the
jb_claude_pluggins
jb-finance-mcp-plugin) reports connection status (or "not
connected"/"EXPIRED") and a live balance check for every onboarded
institution:
uv run python skills/connect-bank-account/scripts/check_bank_status.py --live(run from a clone of that plugin's repo, at its own root directory — see
its README for the one-time uv sync setup; the Claude Code plugin
install itself doesn't give you a directory to cd into by hand).
Environment variables
Variable | Default | Purpose |
|
| Identity used for every policy check and audit entry in this process. Must match a |
|
| Path to the policy file. Must exist and be valid YAML — the server fails to start if it's missing, even just to serve |
|
| Path to the audit log (JSON Lines, one entry per tool call, secrets redacted). Parent directory is created automatically. |
Tool catalog
Tool | Scope | Notes |
| — (ungated smoke-test tool) | Always available, not policy-gated |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| adds counterparty name/description (IBANs still masked) — not granted by default; cached 60min |
Bank tool result caching
Enable Banking enforces a daily, not short-term, per-consent access cap
("consented multiplicity without PSU involvement per day") — a 429 means
that institution's whole day is spent, not "wait and retry." The four
bank.* tools that actually reach the live API (everything above except
bank.list_accounts, which is a local keychain read) cache their result
in memory for 60 minutes, keyed by tool name + exact parameters. A repeat
call with identical parameters within that window returns the cached
result instead of making another live request — still fully audit-logged
(outcome: "cached", same params, distinguishable from "success" in
JB_GATEWAY_AUDIT_LOG), just without reaching the handler. The cache is
in-process memory only — never written to disk, and cleared on every
server restart. This is opt-in per tool (ToolSpec.cache_ttl_seconds in
src/jb_gateway_mcp/adapters/base.py); write/send tools are never
cached.
Network & ports
The gateway itself listens on nothing. It's a stdio MCP server — the client (Claude Desktop/Code, etc.) launches it as a subprocess and talks to it over the process's stdin/stdout pipes. There's no port, no host, no URL, no listening socket at any point during normal operation — it isn't reachable over the network at all, by design (see DESIGN.md).
The one exception is the one-time onboard-google step: it briefly
starts a local HTTP server on localhost:8080 (via
google_auth_oauthlib's InstalledAppFlow.run_local_server) purely to
catch Google's OAuth redirect after you approve consent in the browser. It
shuts down immediately once the redirect arrives — nothing is listening
before or after that single command runs. If port 8080 is already in use on
your machine, that command will fail; there's currently no flag to change
the port, so free up 8080 or temporarily stop whatever else is using it
before running onboard-google.
Troubleshooting
"no grant for caller X on tool Y" — expected deny-by-default behavior. Add the grant to
policy.yamlunder the caller id you're using.Re-consent error mentioning a revoked/expired refresh token — re-run
onboard-googlefor that account.403 Forbiddenfromonboard-bank— the Enable Banking application (or that specific institution) hasn't been through "Activate by linking accounts" in their Control Panel yet — see §7a step 4.multiple ASPSPs matched institution=...fromonboard-bank— the institution name is genuinely ambiguous in that country (e.g. "DNB" vs. "DNB Corporate Mastercard"); narrow_INSTITUTION_NAME_HINTinsrc/jb_gateway_mcp/cli/onboard_bank.pyfor that alias and retry.NeedsReconsentError/ "consent ... expired" from a bank tool call — the 90-day bank consent lapsed; re-runonboard-bank --institution <alias>.Audit log — every call (
success,cached,denied, orerror) is recorded atJB_GATEWAY_AUDIT_LOG. Tokens/secrets are redacted before writing.cachedmeans abank.*tool returned a result from the in-memory cache instead of reaching the live API — see "Bank tool result caching" above.
Security notes
Never commit
client_secret.json, any Enable Banking.pemprivate key, or any file matching*credentials*.json—.gitignoreblocks these as a backstop, but treat it as a backstop, not a guarantee.Tokens and bank private keys live only in the OS keychain; they're never logged, never returned in a tool response, and never appear in an audit log entry — the audit log only ever records tool call parameters, never results.
gmail.send_messageandcalendar.create_eventare the only write-capable Google tools; they are not granted in the defaultpolicy.yaml— add them deliberately, only for callers that actually need them.Bank tools are architecturally read-only — the adapter's HTTP helper only ever issues GET requests; there is no code path capable of initiating a payment, even though Enable Banking's API separately supports one. Every IBAN (the account holder's own, and any transaction counterparty's) is masked to its last 4 digits before it leaves the adapter.
bank.list_transactions_detailedis the only tool that surfaces counterparty names/payment descriptions, and it requires its own, off-by-defaultpolicy.yamlgrant — the default tool set never sends that level of financial detail into an agent's context.
Uninstalling
Deleting the repo folder alone is not enough — stored tokens and the Google-side consent grant live outside it. Full teardown, in order:
Remove it from every client you connected it to:
Claude Desktop — delete the
jb-gateway-mcpentry fromclaude_desktop_config.json, then restart Claude Desktop.Claude Code — remove/delete
.mcp.json(project-scoped), orclaude mcp remove jb-gateway-mcpif you registered it globally instead.Any other client — remove its equivalent
mcpServersentry.
Run the uninstall command — revokes the account's grant on Google's side (RFC 7009 token revocation) and deletes its token from the OS keychain, in one step:
uv run uninstall-google --account you@example.comPrompts for confirmation per account (add
--yesto skip); repeatable with multiple--accountflags to clean up more than one at once. If the network call to Google fails, it still deletes the local keychain entry and tells you to revoke access manually at myaccount.google.com/permissions —--keep-remote-grantskips the network call entirely and only deletes locally (e.g. if you already revoked access on Google's side, or the grant was for a different app).Deleting the repo without running this leaves the token sitting in your keychain, and the grant active on Google's side, indefinitely.
Delete local state you don't want lingering (all outside the repo, so
rm -rf-ing the project directory won't touch these):Audit log:
JB_GATEWAY_AUDIT_LOG(default~/.jb_gateway_mcp/)Your
client_secret.jsoncopy, wherever you stored it outside the repo
Remove the project itself:
rm -rf /path/to/jb_gateway_mcp # deletes .venv and all repo files together
Steps 1–4 are the parts people usually forget — the repo directory is the least sensitive thing to clean up here.
Uninstalling bank access
There's no uninstall-bank command yet (unlike uninstall-google) — bank
access is currently removed in two manual steps instead of one:
Revoke on Enable Banking's side — in their Control Panel, revoke the linked account or delete the application entirely. This is the step that actually matters for security; it's the equivalent of myaccount.google.com/permissions for banks.
Delete the local keychain entries —
keyringstores these under the OS's native secret store (Keychain on macOS, Credential Manager on Windows, Secret Service on Linux), under service namesjb_gateway_mcp:enablebanking_app(the app credential, one entry) andjb_gateway_mcp:enablebanking_session(one entry per institution alias you onboarded, e.g.dnb/nordea/revolut). Search forjb_gateway_mcp:enablebankingin your OS's credential manager UI (e.g. Keychain Access.app on macOS) and remove them, or delete a specific institution's private key file if you also want that gone (~/.secrets/jb_gateway_mcp/enablebanking/).
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseAqualityCmaintenanceLocal-first MCP server for agents that need to work across multiple Gmail and Microsoft 365 accounts without cloud token storage.Last updated6
- AlicenseBqualityDmaintenanceA Model Context Protocol (MCP) server that connects AI agents to Google Workspace (Gmail, Calendar, Drive, Docs, Sheets, and Slides).Last updated302MIT
- AlicenseAqualityBmaintenanceAn MCP server that gives AI agents permission-gated, audit-logged access to private email providers (Proton Mail via Bridge and plain IMAP), running locally with OAuth-based authentication and human-controlled escalation for destructive operations.Last updated661988MIT
- Alicense-qualityAmaintenanceLocal MCP server for reading/sending email via Gmail and managing Google Calendar events, enabling an AI agent to handle email and calendar operations through natural language.Last updated1MIT
Related MCP Connectors
Hosted Google Calendar MCP server for AI agents. No self-hosting or Google Cloud setup.
Streamable HTTP MCP server for Google Calendar and Sheets with OAuth login.
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/jitheshb83/jb_gateway_mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server