Skip to main content
oauthProxy needs one store that every instance of your MCP server shares. Pass it as the storage option. Adapters for common databases are in mcp-use/oauth/storage; each one takes a client or binding that your application already has, so mcp-use adds no database dependency.

What the proxy stores

The proxy encrypts every value with a key derived from your secret before the store sees it. Keys hold SHA-256 hashes of tokens, never the tokens themselves, so a leaked store holds no usable token without the secret. One completed sign-in writes these records: Every key starts with mcp-use:oauth: and a hash of the MCP resource URL, so several MCP servers can share one database without seeing each other’s records. A client: record exists only for clients that registered through dynamic client registration. The proxy also writes short-lived markers and locks (codeused:, ustate:, revoked:, lock:), which expire within an hour.

Adapter requirements

A store must:
  • be shared by every instance of the server, because a sign-in can start on one instance and finish on another;
  • expire each value after the ttlSeconds it was written with;
  • implement take (read and delete) and add (write if absent) atomically. One-time codes and sign-in states depend on take, and refresh locks depend on add.
Workers KV alone does not qualify: it has no atomic read-and-delete, so two requests could spend one authorization code. Use D1 or a Durable Object on Cloudflare.

Upstash

upstashStorage needs only fetch. Pass a fetch option to use a custom implementation.

Redis

Pass a connected node-redis or ioredis client, or a function that connects one:
With a function, the adapter connects on the first storage call, not when the module loads, so serverless cold starts never wait on Redis. Concurrent first calls share one connection attempt, and a failed attempt is retried on the next call. A wrong URL therefore shows up at the first sign-in, not at startup. The adapter uses GETDEL and SET NX EX, which need Redis 6.2 or later.

Postgres

The adapter creates the table on first use:
take uses DELETE … RETURNING. Expired rows are deleted in the background on about 1% of writes. table must be a plain SQL identifier.

Firestore

Each key is one document with a value field and an expiresAt timestamp. take and add run in transactions. The adapter ignores expired documents, but Firestore keeps them until they are deleted: add a TTL policy on the expiresAt field of the collection. firestoreStorage uses the server SDK, which needs Node.js. It does not run on Cloudflare Workers or other edge runtimes. The Admin SDK bypasses security rules, so the collection needs no rules of its own.

Cloudflare

D1

The adapter creates its table on first use and stores expiry as Unix milliseconds.

Durable Object

A Durable Object processes one request at a time, which makes every operation atomic. Export OAuthProxyDurableObject from your Worker and bind it:
src/worker.ts
wrangler.jsonc
Every key goes to one object named mcp-use-oauth. The object deletes expired entries in pages of 1,000 every 200 writes.

Memory

memoryStorage() keeps values in process memory. Its state is lost on restart and is not shared between instances, so oauthProxy accepts it only when the MCP resource is on localhost. A listen() on a loopback address uses it by default when you pass no storage. Under mcp-use dev, pass it explicitly:
Every reload of mcp-use dev starts a new process, so sign-ins end on each file change. Use Redis locally to keep them.

Write your own adapter

Implement OAuthProxyStorage. Values and keys are strings, and ttlSeconds is always a positive integer:
A store that is not atomic breaks one-time codes: a replayed authorization code could be exchanged twice. Test take and add under concurrency before you deploy.

Next steps

OAuth Proxy

Configure the upstream provider, clients, and consent page.

API reference

Every adapter, option, and client interface.