Tech Breakdown PCP: a gateway you control between your data and the cloud LLMs
The short version: PCP is a self-hosted MCP gateway. Your assistant connects to one endpoint; behind it are every MCP server and REST API you use, with the credentials encrypted on your side and a per-tool permission model you control. It's open source (MIT) at github.com/kaperkunde/pcp. The reasons for building it are in the announcement; this post is about how it works, feature by feature, and the design choices that mattered.
The detour through local
My first attempt at this problem was OpenClaw: an agent that runs on your own hardware, so nothing leaves the house. It's a good idea and I enjoyed it. It also lost the race it was in. The models good enough to plan a multi-step task and recover when a tool call fails kept growing faster than what a Mac mini can run, and the gap isn't closing. For the foreseeable future the best reasoning is cloud-hosted.
But the two reasons I went local in the first place didn't go away:
- My credentials shouldn't live with the AI vendor. Every "connect your Gmail" button hands a long-lived token to a company whose product improves with more of your context.
- I don't want to be locked in. Today's prices are subsidised by venture capital. When that runs out, the services holding your integrations are the ones that can raise prices without losing you.
So the design became: use the cloud brain, keep the hands at home. PCP is a middleman you run, between your data and whichever LLM you're renting this year, and its job is gating access.
One endpoint, three tools
The usual way to give an assistant many services is to attach many MCP servers. Each one's full tool list, with descriptions and JSON schemas, goes into the context on every turn whether you use it or not. Ten servers can cost 20,000 tokens before you've typed anything, and some plans cap you at one connector anyway.
PCP presents three tools instead: search_tools (a few words in, ranked server/tool lines out), describe_tool (the full schema for one), and call_tool. My own setup has around 500 tools behind PCP. The assistant pays for the three it always has, a short instructions string naming the servers, and then only for the tools it actually looks up. A conversation that sends one email loads one schema. It also means a 300-tool API, which no client would let you attach directly, is just another server in the list.
Secrets that never leave
Each vault has a random 256-bit data key. Everything sensitive is AES-256-GCM ciphertext under it, with the row id as associated data so a ciphertext can't be moved between rows. The data key itself is stored only wrapped, once per credential: under a key derived from your password (scrypt), from a session cookie, from an API token, or from a recovery key (HKDF). Each request unwraps the key for its own duration and drops it.
So there is nothing on the server that can read the vault on its own. Someone with the disk has ciphertext and hashes. There's no admin key, no environment-variable master secret, and no password reset that doesn't go through the recovery key. Lose both and the data is gone; that's the design, and the setup page says so.
At call time PCP adds the credential last, after the assistant's arguments have been turned into a request, so no argument can replace it, and it scrubs the credential from the answer before the assistant sees it, in case an API echoes a key back in an error. The assistant can name a secret ("use my Linear key"); it never receives one.
Permissions per tool, and why the fancy options lost
Every API token has a level per tool: allowed, ask (the default) or blocked. A call to an "ask" tool becomes a permission request with the arguments encrypted on it, and the assistant gets back "Not done yet" with a link, which it ends its reply with. You answer on PCP's own page, signed in, either from that link or from the bell in PCP's header, which counts and lists everything waiting. Tell the assistant you've answered and it calls check_permission for the result (which holds on for up to 45 seconds if you're still mid-answer). "Always allow" and "Block" settle the tool for next time, and an assistant can propose levels for a whole server at once, which you review and save; it can't raise its own access.
I tried the clever versions first. MCP elicitation (the client shows a form) and MCP Apps (PCP renders its own panel in the chat) are both in the spec, and Claude's clients declare support for both. In practice they stalled on "Loading…" until the call timed out, and the panel was rebuilt stale every time the conversation re-rendered (issue). A link is less elegant and works in every client, so PCP does only that now. Even the link took a lesson: Claude's apps fold any text written before a tool call into a collapsed row with a summary of their own, so a link followed by "I'll wait for your answer" was often never seen. The link goes last now, with nothing after it.
"Add Gmail in PCP"
This is the part I'm most pleased with. There's no Gmail recipe in PCP. There's no recipe for anything. Here is what actually happens when I type that into Claude:
- Claude searches the web and finds that Google requires permission to use Gmail as MCP server for Gmail, but the REST API is documented in full.
- It writes an OpenAPI 3 document for the operations I'm likely to want, with the
oauth2security scheme pointing at Google's authorization and token endpoints, and calls PCP'sregister_serverwith it,auth_type: oauth, and a name. - PCP parses the document, generates a tool per operation (a JSON Schema for the arguments and a call plan: method, path, which argument goes where, how the body is encoded), and shows me an approval page: the address requests will go to, the operation count and names, which ones write, where the sign-in happens, and the redirect URI my Google client needs.
- Google won't let apps register themselves, so I need a client ID. Claude knows that too, and gives me the steps: Cloud Console, enable the Gmail API, create an OAuth client, paste in the redirect URI from PCP's page, set
access_type=offlineso the sign-in is renewable (PCP adds that one itself). I type the client secret into PCP's page, never into the chat, approve, and sign in.
Gmail is now 76 tools behind the same endpoint. The schema was written by the assistant, approved by me, and the credential went from Google to PCP without passing through the conversation. Self-hosting against Google's OAuth is a bit of a pain, but it's a well-worn path, and the assistant has walked it before.
The same works for the small stuff. Sonarr and Radarr have REST APIs and nobody is going to ship an MCP server for them; now "what's downloading?" is a question I can ask. My invoicing software has a legacy API from years before anyone said "agent". An assistant that can read documentation and write a schema turns every one of those into something it can use.
Smoothing the rough edges
The first schema is never quite right. An example in the docs is the wrong type, a header the API always wants isn't in the document, an operation never says what it returns. So the assistant fixes schemas with edits instead of resending them: update_endpoint takes a JSON Patch, PCP keeps it and reapplies it every time the schema is read, and get_endpoint reads one part of the document at a time by JSON Pointer so the assistant can walk a 5 MB schema without blowing its context. A built-in linter lists the usual mistakes with the patch that fixes each. The loop is: try the call, read the real error from the real API, patch the schema, try again. The rough edges get smoothed by feedback rather than by someone maintaining a connector.
Answers get shaped on the way back too. call_tool takes a list of fields to keep from a large JSON response, and decode paths for the base64 bodies Gmail sends, so one message doesn't cost 40 KB of context.
What the assistant may not do
An assistant that can register endpoints decides where PCP sends requests, so the rules here are strict:
- It never sees, chooses or changes a credential. A secret is named, not sent; a new one is typed by you on PCP's page.
update_endpointaccepts nothing that names a secret or a header. - Its endpoints reach public addresses only, checked on the address the socket actually connects to (PCP resolves the name itself), so DNS rebinding doesn't get past it. Only you can allow private addresses, which is what makes the Sonarr example work.
- A schema it fetched by URL is approved as that copy. Whoever controls the URL can't add operations later by changing the file.
- Once an endpoint carries your secret or OAuth token, it's yours. The assistant can read it and turn read-only on, and anything else it wants there becomes a request showing every proposed change in full. The address and a whole new schema are never on offer.
- A change other assistants would see disables the endpoint until you enable it. Tool descriptions reach every assistant through
search_tools, so they're yours to approve.
Memories that move with you
A token can also be given a memory tool, modelled on Claude's own: files under /memories that persist between conversations, but held by PCP rather than the vendor, so they come with you when you switch assistants. An assistant writes its own freely. Anything under /memories/shared/ is read by every assistant, so creating or changing one is a permission request that shows you the whole text, with hidden Unicode refused so what you read is what's there. You can mark a memory to be read in every conversation; only you can set that.
No lock-in
Everything above lives in PCP: the servers, the schemas and their fixes, the permissions, the memories, the credentials. The assistant holds one bearer token. Switching from Claude to ChatGPT, or to whatever comes next, is pasting one URL into the new client. Nothing to re-authorise, nothing to rebuild, and the vendor you're leaving never had your keys.
What it doesn't solve
Once you've approved an endpoint and allowed a tool, the assistant can send data it holds to that address as arguments. A prompt injected into the assistant can do the same. PCP makes that visible and makes it ask; it can't make it impossible. The same goes for a malicious upstream lying in its tool results. Schema text and tool descriptions are stored in the clear, like server addresses. The SECURITY.md lists all of it.
Stack, and running it
Next.js 15 with Server Actions, Prisma on SQLite, the MCP TypeScript SDK v2 for both the gateway and the upstream client, one container, migrations at boot. docker compose up, open the site, choose a password, add a token, paste the URL into your assistant. No environment variables. Put TLS in front of it if it's reachable from outside.
If you try it and something's wrong, open an issue. If you want the same thing built for your business, you know where to find me.
FAQ
What is PCP?
A self-hosted, open source MCP gateway. Your AI assistant connects to one endpoint and reaches every MCP server and REST API you've added, with credentials encrypted on your side and a per-tool permission model you control.
Does the assistant ever see my API keys?
No. Keys are encrypted at rest under a key only your password unlocks. PCP adds them to upstream calls and scrubs them from answers. An assistant names a secret; it never receives one.
Can it connect to an API that has no MCP server?
Yes. Give PCP an OpenAPI 3 document, by file, URL or from the assistant itself (my favorite), and each operation becomes a tool. The assistant can write the document from an API's docs and register it; nothing exists until you approve what it proposes.
Why a search tool instead of loading every tool?
Context. Every attached MCP server puts its whole tool list into every turn. PCP's three tools cost a fixed, small amount, and the assistant loads only the schemas it looks up, so hundreds of tools behind one connector cost less than a handful attached directly.
Which assistants work with it?
Any client that speaks MCP over Streamable HTTP with a bearer token: Claude (web, desktop, Code), ChatGPT, and others. The toolbox stays with PCP, so switching assistants means pasting one URL.
Kevin Lohman runs Kaperkunde, an independent software consultancy in Amstelveen, and builds Plek.je. Twenty years of iOS and full-stack engineering at Apple, Meta, BlackBerry and Grindr. PCP is at github.com/kaperkunde/pcp.
Member discussion