# Build an MCP server for your internal data

Give agents read-only access to the records they need, behind a login, with nothing else exposed.

- Category: Operations
- Runs on: Claude Code
- Level: Advanced
- Audience: both
- Setup time: about 180 minutes
- Evidence: Documented (each step backed by official docs; not tested by us)
- Last updated: 2026-10-31
- Named sources: 11
- Canonical URL: https://crimsonbot.com/use-cases/internal-data-mcp-server

> **Warning:** Security. Cloudflare's quick-start template is authless: 'anyone can connect and use the server'. Never point an authless server at internal data. Use test data only until sign-in (step 3 or 4) and the step 6 tests pass.

## You will need

- A Cloudflare account (Workers has a free plan; Workers Paid starts at $5 a month)
- Your data in a store the Worker can read, for example Cloudflare D1
- An identity provider for sign-in (GitHub, Google, Cloudflare Access or your own)
- A written list of the records and fields agents may read

## You will end up with

- A remote MCP server that rejects any request without a valid sign-in token
- One read-only tool per record type, returning only the fields on your list
- Tools shown only to users who are allowed to use them

## Steps

### 1. List what agents may read

Write down each record type and field agents should see. Leave out anything sensitive. This list is what you will test against in step 6.

### 2. Build the server with the Agents SDK

Create a remote MCP server on Workers with the Agents SDK (createMcpHandler). Create one tool per record type, each with a narrow input schema (for example with Zod) and a handler that runs a fixed, parameterised SELECT. Mark the tools readOnlyHint: true, but do not rely on that hint for security: clients treat annotations as untrusted, so the real protection is that your handlers only read.

Sources:
- [Cloudflare docs: Build a Remote MCP server](https://developers.cloudflare.com/agents/model-context-protocol/guides/remote-mcp-server/)
- [Cloudflare docs: MCP Tools](https://developers.cloudflare.com/agents/model-context-protocol/protocol/tools/)
- [Cloudflare D1](https://developers.cloudflare.com/d1/)
- [MCP spec 2025-06-18: Schema (ToolAnnotations)](https://modelcontextprotocol.io/specification/2025-06-18/schema)
- [MCP spec 2025-11-25: Tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools)

### 3. Require sign-in (OAuth) before any tool runs

Do not deploy the authless template with real data: Cloudflare's guide says that without authentication 'anyone can connect and use the server'. Instead, build an authorization server Worker with Cloudflare's Workers OAuth Provider library (OAuthAuthorizationServer). Its /authorize page signs users in through GitHub, Google, Cloudflare Access or another identity provider. Wrap your MCP handler in OAuthResourceServer: it answers requests without a valid token with a 401 and rejects tokens issued for any other resource. MCP clients follow the 401, sign the user in, and retry with a token. This is the OAuth 2.1 flow the MCP specification defines.

Sources:
- [Cloudflare docs: Build a Remote MCP server](https://developers.cloudflare.com/agents/model-context-protocol/guides/remote-mcp-server/)
- [cloudflare/workers-oauth-provider on GitHub](https://github.com/cloudflare/workers-oauth-provider)
- [Cloudflare docs: MCP Authorization](https://developers.cloudflare.com/agents/model-context-protocol/protocol/authorization/)
- [MCP spec: Authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)

### 4. Alternative: put it behind Cloudflare Access

If your team already uses Cloudflare Access, it can sign users in to the MCP server with your identity provider or a one-time PIN, and only lets in people who match your Access policies.

Sources:
- [Cloudflare docs: MCP Authorization — Cloudflare Access](https://developers.cloudflare.com/agents/model-context-protocol/protocol/authorization/)
- [Cloudflare One docs: Secure MCP servers with Access](https://developers.cloudflare.com/cloudflare-one/access-controls/ai-controls/secure-mcp-servers/)

### 5. Limit tools by user

Use the signed-in user's details (getMcpAuthContext) to check permissions inside each tool, or register only the tools a user may use. Never log or return the props, since they can hold access tokens.

Sources:
- [Cloudflare docs: MCP Authorization — permission-based tool access](https://developers.cloudflare.com/agents/model-context-protocol/protocol/authorization/)

### 6. Test every tool before connecting agents (human checkpoint)

Use the MCP Inspector to call each tool, signed in as a normal user and with no token at all. A request with no token must get a 401. Each tool must return only the fields on your list. Keep a person able to deny tool calls in the client.

Sources:
- [MCP Inspector](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector)
- [MCP spec 2025-06-18: Tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)

## Tools used

- [Cloudflare Agents SDK](https://developers.cloudflare.com/agents/): Stateful agents and MCP on Workers. v0.32 adds Agent Skills (breaking) and private-app browsing.

## Next

- [Our tool: Scrub a PDF locally before you paste it into AI →](https://crimsonbot.com/go/p/pdf-scrubber)
- [Get an email when the newsletter launches →](https://crimsonbot.com/#newsletter)
