Skip to main content
Glama

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 api.github.com

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 redacted
  • Sign-in: the server sits behind Google sign-in. Only emails you list in ALLOWED_EMAILS can 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?

api_request

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:

auth_type

Sends

Use for

bearer (default)

Authorization: Bearer <token>

GitHub, Notion, Stripe, Slack, OpenAI, most APIs

header

<header_name>: [<header_prefix> ]<token>

APIs with a custom header, e.g. X-API-Key, or Linear (Authorization: <key>)

basic

Authorization: Basic ... (secret is user:password, or pass username and store just the password)

Jira / Confluence (email:api_token), Twilio

query

?<query_param>=<token>

APIs that take the key in the URL

aws_sigv4

AWS Signature V4, from secrets AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY (and optional AWS_SESSION_TOKEN)

Any AWS API: S3, STS, EC2, ...

Managing your secrets

Tool

What it does

Needs your approval?

list_secrets

Lists secret names, folders, notes and the hosts each may be sent to. Never values.

No

get_secret

Shows one secret's details. Never the value.

No

add_secret

Stores a new token with the hosts it may be sent to

No

update_secret

Changes a secret's note, value or name (hosts never change)

Yes, for a new value or name

delete_secret

Deletes a secret permanently

Always

list_folders

Lists folders in the vault

No

list_environments

Lists vault environments (e.g. dev, prod)

No

Optional extras

Tool

What it does

Needs your approval?

get_upload_url

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

issue_temp_token

Gives Claude a short-lived (1 hour max) vault token for real CLIs like aws or terraform

Always, with a must-tick box

access_guide

Setup help text for issue_temp_token

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-deps is 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.dev

Find <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_KV

Copy the id it prints.

4. Set up Infisical (your vault)

  1. Create a project in Infisical. Copy its Project ID (Project settings).

  2. Create a Machine Identity with Universal Auth (Organization → Access Control → Identities).

  3. Create a Client Secret for it. Copy the Client ID and Client Secret.

  4. 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_secret etc.

  5. Note the environment you'll use (default: dev).

5. Set up Google sign-in

  1. In Google Cloud Console → APIs & Services → OAuth consent screen: choose External, fill in the app name, and add your email as a test user.

  2. Credentials → Create credentials → OAuth client ID → type Web application.

  3. Under Authorized redirect URIs add:

    https://infisical-mcp.<your-subdomain>.workers.dev/callback
  4. Copy 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_SECRET

8. Deploy

npx wrangler deploy

9. 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 yet

Deploy from GitHub instead? .github/workflows/deploy.yml runs wrangler deploy when you start it by hand (Actions → Deploy → Run workflow). Add repo secrets CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID first. To deploy on every push to main, add a push: trigger in that file.


Connect it to Claude

Your MCP endpoint is:

https://infisical-mcp.<your-subdomain>.workers.dev/mcp

claude.ai and Claude Desktop

  1. Settings → Connectors → Add custom connector

  2. Name: Universal API. URL: your /mcp endpoint.

  3. Click Connect and sign in with a Google account listed in ALLOWED_EMAILS.

  4. 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/mcp

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

The token never touches the chat.

  1. Open your Infisical project → environment dev.

  2. Create a folder per service, e.g. /github, /notion, /linear.

  3. Add the secret inside it, e.g. name GITHUB_TOKEN, value ghp_....

  4. In the secret's comment, add a hosts line:

    hosts: api.github.com, uploads.github.com
  5. Done. 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 (no https://, path, port or IP).

  • Wildcards cover subdomains: *.atlassian.net matches yourteam.atlassian.net, but never the bare atlassian.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

hosts: line

How Claude calls it

GitHub

GITHUB_TOKEN

api.github.com, uploads.github.com

bearer (default)

Notion

NOTION_TOKEN

api.notion.com

bearer + header Notion-Version: 2022-06-28

Linear

LINEAR_API_TOKEN

api.linear.app

header, header_name: Authorization

Stripe

STRIPE_SECRET_KEY

api.stripe.com

bearer

Slack

SLACK_BOT_TOKEN

slack.com

bearer

OpenAI

OPENAI_API_KEY

api.openai.com

bearer

Jira / Confluence

JIRA_TOKEN (value email:api_token)

*.atlassian.net

basic

Vercel

VERCEL_TOKEN

api.vercel.com

bearer

AWS

AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY

*.amazonaws.com is refused, so list exact hosts, e.g. sts.us-east-1.amazonaws.com, my-bucket.s3.us-east-1.amazonaws.com

aws_sigv4

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_secret

  • update_secret with a new value or a new name

  • api_request calls that look destructive:

    • method DELETE

    • GraphQL mutations whose names contain delete / destroy / remove / archive / purge / trash (e.g. Linear issueDelete)

    • JSON bodies that set archived, trashed, deleted or in_trash to true (how Notion and GitHub archive)

  • issue_temp_token (always)

What you see:

  1. Claude says the action needs approval and gives you a link.

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

  3. The page shows the exact action (URL, method, body, secret). Press Approve or Deny.

  4. 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_KEY

Then 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_SECRET

Optional: 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_secrets and get_secret never return values. Every response from api_request has 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_request never 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 Authorization header, and method-override headers are refused, so a DELETE can't be disguised.

  • Human approval for deletes, overwrites and temp tokens, as described above.

  • Sign-in allowlist. Only ALLOWED_EMAILS can 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_request capped at 5 MB (use get_upload_url for bigger files)

  • Detecting non-DELETE destructive 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_token with the Infisical CLI.


Troubleshooting

Problem

Fix

"Secret X is not allowed to be sent to &lt;host&gt;"

The secret's hosts: line doesn't include that hostname. Edit the comment in Infisical.

"Secret X exists in several folders"

Two secrets share the name. Pass path, or rename one.

Google says redirect_uri_mismatch

The redirect URI in Google Cloud must be exactly <PUBLIC_URL>/callback.

"This Google account is not allowed."

Your email isn't in ALLOWED_EMAILS. Add it and redeploy.

/mcp returns 401

Expected before sign-in. Connect through Claude, which runs the sign-in flow.

Infisical errors (401/403)

Check INFISICAL_CLIENT_ID / INFISICAL_CLIENT_SECRET, that the identity is added to the project, and that INFISICAL_HOST matches your region (US/EU).

get_upload_url or issue_temp_token only return setup steps

Their optional setup isn't done yet. See Optional extras.

npm install fails

Use npm install --legacy-peer-deps.


Local development

Create .dev.vars (git-ignored):

INFISICAL_CLIENT_ID=...
INFISICAL_CLIENT_SECRET=...
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
PUBLIC_URL=http://localhost:8787

Add 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 push

There'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.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables 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.
    1
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables 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.
    6
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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 npm
    MIT
  • F
    license
    A
    quality
    F
    maintenance
    Enables 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
    -