# Case 02: Building MCP connections

**What changed:** Each person’s agent reaches only what that person may see, with no shared keys.

MCP is the standard way an AI agent uses a real tool. I built connectors that speak it and hardened the gateway every call passes through, so agents could use company apps without ever holding a key.

Stack: Go, MCP, OAuth 2.0 with PKCE, token vault, structured logs and metrics

## How a tool call flows

1. **Agent asks for a tool.** An agent decides it needs outside data, so it calls a tool over MCP.
2. **Gateway checks the grant.** The gateway confirms this agent holds that tool’s permission, and that the tool isn’t switched off.
3. **Vault attaches the user’s token.** The vault adds the signed-in person’s own token. The agent never sees it.
4. **The MCP server calls the app.** The connector turns the tool call into a request to the real app.
5. **Only what this person may see.** The app answers with what this person is allowed to read, and the answer flows back.

## At a glance

- **My role:** Built the per-user OAuth (PKCE) connector and its MCP server. Audited the tool gateway and shipped its reliability fixes.
- **Scope:** Every tool call from every agent on the platform passes through this gateway.
- **Stack:** Go · MCP · OAuth 2.0 with PKCE · token vault · structured logs and metrics
- **Shipped:** Live in production: each person’s agent searches only what that person may see. Gateway: per-backend timeouts, isolated cancellations, metrics.

## The decisions that shaped every connector

1. **We refused a shared key.** Each person signs in with their own account, using OAuth with PKCE. Apps check permissions against the token’s owner, so a shared company key would have shown everyone the same documents. (Per-user access)
2. **The vendor ignored our rules, so we moved them.** The gateway enforces permissions before the vendor is ever contacted. Vendor-hosted servers run none of our code, so they ignored our permission headers. (Gateway)
3. **Then we built our own.** Our own MCP server publishes its tools, checks permissions itself, and never keeps a token between requests. The vendor’s server couldn’t, and our copy of its tool list kept drifting. (Tested end to end)

## One name, one permission

Every tool is named `service_product_action` and carries exactly one permission. An agent can only call what its owner granted, checked before the app is contacted.

- `knowledge_search_query`: Search (permission `knowledge:search:read`)
- `knowledge_chat_ask`: Ask a question (permission `knowledge:chat:read`)
- `knowledge_documents_read`: Read a document (permission `knowledge:documents:read`)

## How I tested it

### 1. Against a fake app

Before any real app was involved, a fake one checked that each person’s token is used only for them, and that a request without one is refused.

```
go test ./...
✓ the user’s token becomes the outbound credential
✓ back-to-back callers never share a token
✓ no credential → refused, the app is never called
✓ a tool without permission never reaches the app
✓ the tool catalogue is served without credentials
ok   connector-mcp   all green
```

### 2. With a real teammate

A teammate who didn’t build it asked their own agent a normal question, without naming the connector. The agent chose the right tool by itself, found the answer in a ticket, and named the tools it used so the answer could be checked.

## Where it plugs in: many apps, one gateway

- **Company knowledge:** search, ask, read documents
- **Observability:** logs, metrics, monitors
- **Team chat:** read and write, scoped
- **Travel:** flights and stays
- **Code:** repositories and pull requests
- **Data:** saved queries

What broke along the way, and the fix:

- A cancelled call failed its neighbours: each caller isolated; the upstream wait bounded.
- A connector failed every call, silently: caught by the new logs; routing fixed.
- Callers could set control headers: stripped at the edge.
- Status checks disagreed: aligned into a single answer.

## Lessons

- **Permissions belong to people, not keys.** A shared key would have shown everyone the same documents. Per-user sign-in cost more work and was the only honest answer.
- **Don’t outsource your rules.** A vendor server ignored our permission headers, so enforcement moved to the gateway, before the vendor is ever called.
- **Better logs find bugs nobody reported.** The structured logs I added exposed a connector that was failing silently in production. I fixed its routing.
- **Let someone else prove it.** The real test was a teammate asking a normal question and the agent choosing the right tool on its own.

## More

- Previous: [Building an AI agent platform](https://nityashree.in/work/agent-platform.md)
- Next: [Security across an agent fleet](https://nityashree.in/work/security.md)
