Build a Least-Privilege MCP Server for Claude Code
A practical walkthrough for exposing one read-only resource, one narrow tool, and one reusable prompt to Claude Code without handing the model a broad capability surface.
Most MCP demos start with the fun part: connect the server, expose a tool, and watch Claude call it.
That is useful, but it skips the production question: what should Claude not be allowed to do?
MCP is a capability layer. It decides what external systems, tools, resources, and workflows an assistant can discover and use. So the design goal is not "give Claude everything." The goal is to expose the smallest useful capability for the workflow.
In this post, we will build a small project-knowledge MCP server for Claude Code. It can read project guidelines, review a proposed change summary, and offer a reusable review prompt. It cannot deploy, delete, send messages, read secrets, charge customers, or mutate external systems.
What we are building
The server exposes three things:
project://guidelinesas a read-only resource.review_change_summaryas one narrow tool.review-changeas a reusable MCP prompt.
That is intentionally small. For production-minded MCP design, small is usually the point.
1. Start with the connection
Claude Code is the client. Your MCP server is the capability boundary. The client can discover the server, but discovery should not mean broad authority.
Before we write code, keep the mental model simple: Claude asks, the server offers a small surface, and the project decides what is allowed.
2. Introduce the server boundary
The MCP server is where the assistant's possible actions become explicit.
Claude Code can ask for help, but the server decides what to publish. That boundary matters because a server with no tools is safe by default; capability is added intentionally.
3. Expose only useful capabilities
An MCP server can expose resources, tools, and prompts.
Resources are readable context. Tools are bounded actions. Prompts are reusable workflows. The first design decision is not how to wire everything up; it is deciding which capability type is the smallest honest fit.
4. Add the project boundary
The server is only one layer.
Project scope comes from .mcp.json. File safety comes from Claude Code permissions. Runtime enforcement comes from hooks. Least privilege works when those layers agree instead of hoping one layer does everything.
5. Create the TypeScript project
Start with a normal Node and TypeScript setup. The MCP TypeScript SDK gives us a clean API for resources, tools, prompts, and transports. tsx is only for local development; the committed server should still build to dist/.
Use a local stdio transport first. If the goal is to understand the capability shape, do not begin with remote hosting, OAuth, databases, or deployment plumbing.
6. Start with an empty server
Least privilege starts with zero authority. The first server should connect and advertise its purpose, but expose no tools yet.
The instructions are not a hard security boundary. Treat them as a label for the capability: what the server is for, and what it is not for. Enforcement comes later through permissions and hooks.
7. Make guidelines a resource
If Claude only needs to read something, expose it as a resource, not a tool.
This keeps the architecture clear. Resources are context. Tools are bounded actions. Prompts are reusable workflows. When everything becomes a tool, the surface gets noisy and harder to audit.
8. Add one narrow tool
The first action Claude can take is deliberately small: review a text summary and return structured feedback.
The tool does not approve a pull request, deploy code, write files, call Slack, change a database, or touch a payment provider. A good MCP tool should be small enough that you can describe its authority in one sentence.
9. Add a reusable prompt
The prompt gives the human a clean workflow. It does not secretly execute authority.
Claude Code exposes MCP prompts as slash commands. For this server, the workflow becomes /mcp__project-knowledge__review-change, with the user supplying the title, summary, and risk level.
10. Connect it with project scope
Use .mcp.json when the server belongs to the repository and should be shared with the team.
Use Claude Code settings for a different layer: permissions. Here we deny common secret reads like .env, .env.*, secrets/**, and credential files. Do not rely on "Claude probably will not look there" as a boundary.
11. Add a hook policy gate
Now we move from guidance to enforcement.
The server instructions guide. Tool descriptions guide. Permissions restrict file access. The PreToolUse hook blocks risky MCP tool names before they execute. That gives the project a future safety net if broader MCP servers get added later.
12. Test both paths
A good MCP demo proves the allowed path and the denied path.
First, verify the server appears in /mcp and that the review tool returns structured feedback. Then ask for a destructive MCP action and confirm the hook denies it. If you only test the happy path, you have not tested the architecture.
The mental model
The final architecture has three layers.
The capability layer is the MCP server: one resource, one tool, one prompt.
The scope layer is .mcp.json: this server belongs to this project.
The policy layer is Claude Code settings plus a hook: secret reads are denied, and risky MCP tool calls are blocked before execution.
That is the difference between "Claude can use tools" and "Claude can safely use this specific capability in this project."
One subtle point: Tool Search
Claude Code uses MCP Tool Search by default, so MCP tool definitions do not all need to flood the context window up front. That helps with context pressure.
But Tool Search does not replace least privilege. It helps decide when to look up tool details. It does not decide whether a tool should exist, whether it should be trusted, or whether it should be allowed to run.
Keep both ideas separate:
- Tool Search helps context.
- Least privilege helps safety.
Source notes
This post is based on the PDF draft for building a least-privilege MCP server for Claude Code, then cross-checked against the official Claude Code hooks reference (opens in new tab), the MCP TypeScript SDK server guide (opens in new tab), and the MCP TypeScript SDK repository (opens in new tab).
Enjoyed this post?
Get notified when I publish something new. No spam, just fresh content.