Universal API MCP
Allows authenticated Confluence API calls using basic auth with an email and API token.
Allows authenticated GitHub API calls, such as listing issues, using a stored token scoped to GitHub hosts with the token redacted from responses.
Allows authenticated Jira API calls using basic auth with an email and API token.
Allows authenticated Linear API calls using a custom Authorization header, enabling issue and project operations through Linear's API.
Allows authenticated Notion API calls, such as creating pages or tasks, using a stored token scoped to Notion hosts.
Allows authenticated OpenAI API calls using a stored bearer token scoped to OpenAI hosts.
Allows authenticated Slack API calls using a stored bearer token scoped to Slack hosts.
Allows authenticated Stripe API calls using a stored bearer token scoped to Stripe hosts.
Allows authenticated Twilio API calls using basic auth with stored credentials.
Click on "Deploy 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., "@Universal API MCPList my open GitHub issues and add them to my Notion board"
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.
Universal API MCP
Let Claude call any API — GitHub, Notion, Linear, Stripe, Slack, Jira, AWS and more — without ever seeing your tokens.
Universal API is a remote MCP server you deploy once on Cloudflare Workers (free plan). Your API keys live in Infisical, a secrets vault. When Claude needs to call an API, it passes only the secret's name. The server looks up the real token, makes the call, removes the token from the response, and hands back just the result.
No more pasting tokens into chat. No token in the model's context, the chat log, or any prompt.
You: "List my open GitHub issues and add each one to my Notion task board."
Claude: api_request { url: "https://api.github.com/issues", secret: "GITHUB_TOKEN" }
api_request { url: "https://api.notion.com/v1/pages", method: "POST", secret: "NOTION_TOKEN", ... }
→ Done. 7 issues added. (Claude never saw either token.)Contents
Related MCP server: keywarden
Why use it
Without Universal API | With Universal API |
You paste a token into the chat, and it sits in the conversation history | Tokens stay in your vault, and Claude only knows their names |
One MCP server per service (GitHub MCP, Notion MCP, Linear MCP, ...) | One server for every HTTPS API |
A prompt-injected page could trick the model into sending your token somewhere else | Each token is locked to its own hosts; a GitHub token can only ever go to |
Deletes happen as soon as the model decides | Deletes and overwrites need your click on an approval page |
Adding a new service means new code or a new server | Add a token with its hosts. No redeploy. |
Good for: anyone who uses Claude (claude.ai, Claude Desktop, Claude Code) with real accounts and wants it to work across many services safely.
How it works
sequenceDiagram
participant C as Claude
participant W as Universal API (your Cloudflare Worker)
participant V as Infisical (your vault)
participant A as Any API (e.g. api.github.com)
C->>W: api_request { url, secret: "GITHUB_TOKEN" }
W->>V: read GITHUB_TOKEN + its allowed hosts
W->>W: is api.github.com an allowed host? is it destructive?
W->>A: HTTPS request with the token added
A-->>W: response
W-->>C: response with the token redactedSign-in: the server sits behind Google sign-in. Only emails you list in
ALLOWED_EMAILScan connect.Vault: secrets are read live from Infisical, so you add or rotate a token there and it works right away.
Host lock: every secret carries a
hosts:line saying where it may be sent. No line, no sending.
Tools
Once connected, Claude sees these tools. You don't call them yourself; just ask Claude in plain language.
Calling APIs
Tool | What it does | Needs your approval? |
| The main tool. Makes an authenticated HTTPS call to any API using a stored secret, and returns the response with the secret redacted. Supports JSON bodies, query params, custom headers, file uploads (up to 5 MB) and AWS SigV4 signing. | Only for destructive calls (see Approvals) |
Auth types supported by api_request:
| Sends | Use for |
|
| GitHub, Notion, Stripe, Slack, OpenAI, most APIs |
|
| APIs with a custom header, e.g. |
|
| Jira / Confluence ( |
|
| APIs that take the key in the URL |
| AWS Signature V4, from secrets | Any AWS API: S3, STS, EC2, ... |
Managing your secrets
Tool | What it does | Needs your approval? |
| Lists secret names, folders, notes and the hosts each may be sent to. Never values. | No |
| Shows one secret's details. Never the value. | No |
| Stores a new token with the hosts it may be sent to | No |
| Changes a secret's note, value or name (hosts never change) | Yes, for a new value or name |
| Deletes a secret permanently | Always |
| Lists folders in the vault | No |
| Lists vault environments (e.g. | No |
Optional extras
Tool | What it does | Needs your approval? |
| For big files (up to 5 GB): gives a one-time upload link and a temporary download link to pass to other APIs. The file never goes through the chat. | No |
| Gives Claude a short-lived (1 hour max) vault token for real CLIs like | Always, with a must-tick box |
| Setup help text for | No |
get_upload_url and issue_temp_token need extra setup; until then they reply with the setup steps. See Optional extras.
Setup (about 20 minutes)
What you need
A Cloudflare account (free plan is fine)
An Infisical account (free plan is fine), on US or EU cloud
A Google Cloud project (for the sign-in screen; free)
Node.js 20+ and git
1. Get the code
git clone https://github.com/akshay-kumar-bm/universal-api-mcp.git
cd universal-api-mcp
npm install --legacy-peer-deps
npx wrangler login
--legacy-peer-depsis required. Dependencies are pinned on purpose; keep them pinned.
2. Pick your server URL
Your Worker will live at:
https://infisical-mcp.<your-subdomain>.workers.devFind <your-subdomain> in the Cloudflare dashboard under Workers & Pages (shown on the right, e.g. jane.workers.dev). This full URL is your PUBLIC_URL.
3. Create the storage Cloudflare needs
npx wrangler kv namespace create OAUTH_KVCopy the id it prints.
4. Set up Infisical (your vault)
Create a project in Infisical. Copy its Project ID (Project settings).
Create a Machine Identity with Universal Auth (Organization → Access Control → Identities).
Create a Client Secret for it. Copy the Client ID and Client Secret.
Add the identity to your project with a role that can read secrets. Give it create/edit/delete too if you want Claude to manage secrets with
add_secretetc.Note the environment you'll use (default:
dev).
5. Set up Google sign-in
In Google Cloud Console → APIs & Services → OAuth consent screen: choose External, fill in the app name, and add your email as a test user.
Credentials → Create credentials → OAuth client ID → type Web application.
Under Authorized redirect URIs add:
https://infisical-mcp.<your-subdomain>.workers.dev/callbackCopy the Client ID and Client Secret.
6. Fill in wrangler.jsonc
Replace the placeholders:
"kv_namespaces": [{ "binding": "OAUTH_KV", "id": "<id from step 3>" }],
"vars": {
"INFISICAL_HOST": "https://us.infisical.com", // or https://eu.infisical.com
"INFISICAL_PROJECT_ID": "<project id from step 4>",
"DEFAULT_ENVIRONMENT": "dev",
"ALLOWED_EMAILS": "you@gmail.com,teammate@gmail.com", // who may sign in
"PUBLIC_URL": "https://infisical-mcp.<your-subdomain>.workers.dev"
}7. Store the server's own secrets
Each command asks you to paste the value:
npx wrangler secret put INFISICAL_CLIENT_ID
npx wrangler secret put INFISICAL_CLIENT_SECRET
npx wrangler secret put GOOGLE_CLIENT_ID
npx wrangler secret put GOOGLE_CLIENT_SECRET8. Deploy
npx wrangler deploy9. Check it works
curl https://infisical-mcp.<your-subdomain>.workers.dev/.well-known/oauth-authorization-server
# 200 with JSON -> the server is up
curl -i https://infisical-mcp.<your-subdomain>.workers.dev/mcp
# 401 Unauthorized -> correct, you haven't signed in yetDeploy from GitHub instead?
.github/workflows/deploy.ymlrunswrangler deploywhen you start it by hand (Actions → Deploy → Run workflow). Add repo secretsCLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_IDfirst. To deploy on every push tomain, add apush:trigger in that file.
Connect it to Claude
Your MCP endpoint is:
https://infisical-mcp.<your-subdomain>.workers.dev/mcpclaude.ai and Claude Desktop
Settings → Connectors → Add custom connector
Name:
Universal API. URL: your/mcpendpoint.Click Connect and sign in with a Google account listed in
ALLOWED_EMAILS.Enable the connector in a chat (the tools menu) and start asking.
Claude Code
claude mcp add --transport http universal-api https://infisical-mcp.<your-subdomain>.workers.dev/mcpThen run /mcp inside Claude Code and choose Authenticate to sign in with Google.
Other MCP clients
Any client that supports remote MCP over HTTP with OAuth works. Point it at the /mcp URL; it will discover the sign-in flow automatically.
Add your API tokens
Every token needs two things: a name (how Claude refers to it) and hosts (where it may be sent).
Option A: in the Infisical dashboard (recommended)
The token never touches the chat.
Open your Infisical project → environment
dev.Create a folder per service, e.g.
/github,/notion,/linear.Add the secret inside it, e.g. name
GITHUB_TOKEN, valueghp_....In the secret's comment, add a hosts line:
hosts: api.github.com, uploads.github.comDone. Ask Claude "list my secrets" to confirm it shows
approved_hosts.
Option B: ask Claude
"Store my Linear API key as LINEAR_API_TOKEN in /linear, allowed host api.linear.app. The key is lin_api_..."
Claude calls add_secret with the hosts. This is quick, but the value does pass through the chat once, so prefer Option A for important tokens.
Rules for hosts:
Bare hostnames only:
api.example.com(nohttps://, path, port or IP).Wildcards cover subdomains:
*.atlassian.netmatchesyourteam.atlassian.net, but never the bareatlassian.net.Wildcards on shared-hosting domains (
*.workers.dev,*.github.io,*.vercel.app,*.amazonaws.com, ...) are refused, because anyone can register a subdomain there. Name the exact host instead.Hosts are locked at creation. Claude can't change them later, even with
update_secret. Only you can, by editing the comment in Infisical. This stops a tricked model from redirecting a token.A secret with no
hosts:line is never sent anywhere. Use that for IDs and config values.
Common services
Service | Suggested name |
| How Claude calls it |
GitHub |
|
|
|
Notion |
|
|
|
Linear |
|
|
|
Stripe |
|
|
|
Slack |
|
|
|
OpenAI |
|
|
|
Jira / Confluence |
|
|
|
Vercel |
|
|
|
AWS |
|
|
|
Use the narrowest token you can: read-only scopes, one repo, one workspace. The host lock limits where a token goes; the token's own scopes limit what it can do.
Using it
Just talk to Claude. Some things to try:
"What secrets do I have stored?"
"Show my GitHub notifications from today."
"Create a Linear issue titled 'Fix login bug' in the Web team."
"Find the Notion page called Roadmap and add a bullet: Ship v2 in March."
"How much did we make on Stripe this week?"
"Who am I on AWS?" (calls STS
GetCallerIdentity)
Under the hood Claude makes calls like:
api_request { url: "https://api.github.com/user", secret: "GITHUB_TOKEN" }
api_request { url: "https://api.notion.com/v1/users/me", secret: "NOTION_TOKEN",
headers: { "Notion-Version": "2022-06-28" } }
api_request { url: "https://api.linear.app/graphql", method: "POST", secret: "LINEAR_API_TOKEN",
auth_type: "header", header_name: "Authorization",
body: { query: "{ viewer { name } }" } }
api_request { url: "https://sts.us-east-1.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15",
auth_type: "aws_sigv4", aws_service: "sts", aws_region: "us-east-1" }Uploading a file (up to 5 MB) works with multipart or body_base64. For example, attaching an image to a Notion page takes three calls: create a file upload, send the bytes with multipart, then reference the upload id on the page. For bigger files, see get_upload_url.
Secrets in several folders: api_request finds a secret by name in any folder. If the same name exists in two folders, it refuses to guess and asks for path (e.g. path: "/github").
Approvals for risky actions
Some actions need a human, and the server enforces it, not the model:
delete_secretupdate_secretwith a new value or a new nameapi_requestcalls that look destructive:method
DELETEGraphQL mutations whose names contain delete / destroy / remove / archive / purge / trash (e.g. Linear
issueDelete)JSON bodies that set
archived,trashed,deletedorin_trashtotrue(how Notion and GitHub archive)
issue_temp_token(always)
What you see:
Claude says the action needs approval and gives you a link.
You open it. The first time, you sign in with Google. That keeps you signed in for 12 hours, so later approvals are one click.
The page shows the exact action (URL, method, body, secret). Press Approve or Deny.
Tell Claude you approved. It repeats the identical call, which now runs.
An approval works once, only for that exact action (same URL, name and body), and expires after 15 minutes. Claude can't approve for you: it never has your browser session.
Everything else (reads, creates, ordinary updates) runs right away.
Optional extras
Big files: get_upload_url
Move files of up to 5 GB between your machine and other APIs without sending them through the chat. Claude gets a one-time upload URL, runs one curl -T <file>, and receives a download link (valid for 1 hour) to pass to another API. Files are deleted automatically after 1 day.
One-time setup with Cloudflare R2:
npx wrangler r2 bucket create <bucket>
npx wrangler r2 bucket lifecycle add <bucket> --name expire-tmp --prefix tmp/ --expire-days 1
# Cloudflare dashboard → R2 → Manage API tokens → Object Read & Write on that bucket, then:
npx wrangler secret put R2_ACCESS_KEY_ID
npx wrangler secret put R2_SECRET_ACCESS_KEYThen uncomment R2_ACCOUNT_ID (your Cloudflare account ID) and R2_BUCKET in wrangler.jsonc and deploy again.
CLI access: issue_temp_token
Some tools need credentials in their own environment (aws, terraform, ...). issue_temp_token gives Claude a short-lived Infisical token to use with infisical run -- <command>:
It uses a separate, limited "agent" identity, never the server's own.
Every request shows you the purpose, what the token can read, whether it can write, and its lifetime. You must tick a box to give it.
The server refuses identities whose tokens live longer than 1 hour.
Setup: create a second Machine Identity with a short token TTL (15–60 min) and a read-only role, then:
npx wrangler secret put AGENT_INFISICAL_CLIENT_ID
npx wrangler secret put AGENT_INFISICAL_CLIENT_SECRETOptional: set AGENT_PROJECT_ID to a separate small project so temp tokens can't reach your main vault, and AGENT_REQUIRE_READ_ONLY to "true" to refuse identities that can write. Full guide: docs/agent-access.md, or ask Claude to run access_guide.
Security model and limits
What protects your tokens
Values never come back.
list_secretsandget_secretnever return values. Every response fromapi_requesthas the token redacted from its body and headers.Host lock. A token is only sent to the hosts in its own
hosts:line, which is fixed when the secret is created.No redirects.
api_requestnever follows redirects, since the next hop could be a host the token isn't allowed to reach.No secret-in-secret leaks.
${OTHER_SECRET}references are never expanded.No header tricks. Claude can't set its own
Authorizationheader, and method-override headers are refused, so aDELETEcan't be disguised.Human approval for deletes, overwrites and temp tokens, as described above.
Sign-in allowlist. Only
ALLOWED_EMAILScan connect.
Limits
HTTPS only, default port only
25-second timeout per request
Responses capped at 60 KB (the result says when it was truncated)
Uploads through
api_requestcapped at 5 MB (useget_upload_urlfor bigger files)Detecting non-
DELETEdestructive calls is best effort (src/destructive.ts): it recognises the common wording above, but not every API's.This is not a shell. For CLIs use
issue_temp_tokenwith the Infisical CLI.
Troubleshooting
Problem | Fix |
"Secret X is not allowed to be sent to <host>" | The secret's |
"Secret X exists in several folders" | Two secrets share the name. Pass |
Google says | The redirect URI in Google Cloud must be exactly |
"This Google account is not allowed." | Your email isn't in |
| Expected before sign-in. Connect through Claude, which runs the sign-in flow. |
Infisical errors (401/403) | Check |
| Their optional setup isn't done yet. See Optional extras. |
| Use |
Local development
Create .dev.vars (git-ignored):
INFISICAL_CLIENT_ID=...
INFISICAL_CLIENT_SECRET=...
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
PUBLIC_URL=http://localhost:8787Add http://localhost:8787/callback as a redirect URI in Google Cloud, then:
npm run dev # wrangler dev on http://localhost:8787
npm run typecheck # run before every pushThere's no test suite yet. Contributions are welcome.
Infisical endpoints used: v4 list/get/delete secrets and v2 folders are verified against the Infisical docs. v4 create/update and the environments lookup follow the same shape but weren't checked live.
Project layout
src/
index.ts OAuth provider (/mcp, /authorize, /token, /register) around the MCP server
server.ts MCP server and all tools
api-call.ts api_request: auth types, host check, uploads, redaction
hosts.ts validation and parsing of the "hosts:" comment line
destructive.ts detects destructive requests beyond HTTP DELETE
approvals.ts approval records and the /approve pages
approval-store.ts Durable Object for approvals (strongly consistent)
store-core.ts expiry and sweep logic for the store
google-handler.ts Google sign-in, consent page, /callback
infisical.ts small Infisical REST client (Universal Auth)
temp-token.ts issue_temp_token
uploads.ts get_upload_url (R2 presigned URLs)
guide.ts access_guide text
docs/agent-access.md full guide for temporary CLI access
wrangler.jsonc Worker config (fill in the placeholders)License
MIT, see LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Call third-party APIs from agents without leaking secrets. SSRF blocked, per-key quotas.
Secrets for developers and agents—secure context and workflows without exposing secret values.
Give your AI hands. Identity, credential vault, and API gateway for autonomous agents.
Zero-secret MCP gateway for AI agents: risk-scored, audited calls with human-in-the-loop approval.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables LLMs to make HTTP requests with OAuth2, session cookies, retry logic, and cURL command generation, while providing security features like SSRF protection and TLS enforcement.1MIT
- AlicenseAqualityBmaintenanceEnables AI agents to make authenticated API calls and run commands with secrets injected, while keeping credentials completely hidden from the model, with policy enforcement, grants, and audit logging.6MIT
- AlicenseNot gradedqualityCmaintenanceEnables Claude to discover and call any REST API from an OpenAPI/Swagger spec or Postman collection at runtime, with authentication, workflows, and prebuilt registry support.13 npmMIT
- FlicenseAqualityFmaintenanceEnables AI agents to securely store and retrieve secrets and make authenticated API calls on their behalf, keeping API keys out of conversation context and supporting local file or GCP Secret Manager backends.4-