Per-Project MCP Server Configs for Claude Code: Why and How
MCP (Model Context Protocol) servers extend what Claude Code can do. Connect a database server and your agent can query schemas directly. Add a GitHub server and it can create PRs without shell commands. Hook up Sentry and it can pull error reports while debugging.
The problem shows up when you’re working across multiple projects — because different projects need different MCP servers, and managing those configurations manually gets messy fast.
Why one-size-fits-all MCP configs break down
Claude Code looks for MCP configuration in a few places: a global config, a project-level .mcp.json file, and settings files in ~/.claude/. If you set up MCP servers globally, every project gets every server.
That might sound fine until you think about it:
- Your client project connects to the client’s PostgreSQL database. Your personal project connects to your own database. You don’t want those mixed up.
- Project A uses Linear for issue tracking. Project B uses GitHub Issues. Loading both MCP servers into every session adds noise and potential confusion for the agent.
- Some projects need a Figma MCP server for design specs. Others are backend-only and have no use for it.
- Different projects might need the same type of server (e.g., a database connector) but with different connection strings, credentials, or configurations.
Global MCP configs force you to load everything everywhere, or manually toggle servers on and off each time you switch projects. Neither option is great.
The project-level .mcp.json approach
Claude Code supports a .mcp.json file at the root of your project. This is the most common approach for per-project MCP configuration:
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "postgresql://localhost:5432/myproject"
}
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
}
}
}
}
This works, and it’s what I’d recommend as a starting point. Each project gets its own config file with only the servers it needs.
But there are tradeoffs:
Committing to the repo. Everyone gets the same config, but personal setups and credentials become tricky. You’ll need environment variable references and gitignored .env files.
Not committing. Every contributor manages their own config. New contributors start from scratch.
No UI. Adding or removing servers means editing JSON by hand. Not hard, but it’s friction when you’re switching between projects frequently.
What good per-project MCP management looks like
The ideal per-project MCP setup provides four things:
Separation by default
Each project should have its own MCP configuration that loads only when you’re working in that project. No bleed-over.
Easy server management
Adding or removing a server should take seconds, not minutes of JSON editing.
Credential isolation
If Project A’s database MCP server uses one set of credentials and Project B’s uses another, those credentials should never cross. A mistake here isn’t just inconvenient — it’s a security issue.
Quick experimentation
Try a new MCP server on one project without affecting others. Add it, test it, remove it if it doesn’t help.
How Crystl handles per-project MCP configs
Crystl treats MCP configuration as a first-class part of its project model. Each gem (project) has its own MCP server configuration managed through the app’s settings panel.
When you open a gem’s settings, you see its MCP servers listed with their status. Adding a server means filling in the command and environment variables in a form — no JSON editing required. Removing a server is a click.
Because each gem has its own config, there’s complete isolation between projects. Your client project’s database server never shows up in your personal project’s sessions. Credentials stay where they belong.
This pairs naturally with Crystl’s other per-gem settings. Each gem can also have its own API keys — useful if you use different Claude API keys for different clients or want to track usage per project. The gem becomes a self-contained workspace with all its configuration bundled together.
Practical MCP configuration patterns
Regardless of your tooling, these patterns help keep MCP configs manageable:
Pattern 1: Core + project-specific servers
Most projects benefit from a small set of core servers (filesystem, GitHub) plus project-specific ones (database, deployment, monitoring). Set up the core servers in your global config, then add project-specific ones per project.
Pattern 2: Environment-based credentials
Never hardcode credentials in MCP configs. Use environment variable references and manage the actual values through .env files, a secrets manager, or your tool’s credential management:
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "${PROJECT_DATABASE_URL}"
}
}
}
}
Pattern 3: Shared team configs with local overrides
For teams, commit a base .mcp.json with shared servers (GitHub integration, CI tools) and let individuals layer on personal servers through the global config or a tool like Crystl that manages the separation for you.
The cost of getting this wrong
Misconfigured MCP servers aren’t just inconvenient — they can cause real problems. An agent querying the wrong database can read or modify data it shouldn’t. Loading unnecessary servers bloats the agent’s context and can slow down responses.
Per-project MCP configuration isn’t a nice-to-have. As MCP adoption grows and projects rely on more specialized servers, it becomes essential infrastructure for anyone who builds with code across multiple projects.
Set up per-gem MCP configs in Crystl — free — sign up at crystl.dev/login.