Connecting Claude Code to Internal Tools with MCP: A Developer's Guide
A hands-on guide to connecting Claude Code to your internal tools via MCP — setup, real-world use cases, security best practices, and troubleshooting for developers.
Mohammed Kafeel
Machine Learning Researcher
On this page
- What Is MCP (And Why Should You Care)?
- Claude Code + MCP: What's Possible?
- How MCP Works Under the Hood
- Setting Up Your First MCP Server (Step-by-Step)
- Real-World Use Cases: Connecting to Internal Tools
- Security Best Practices
- Troubleshooting Common Issues
- Advanced: Building a Multi-Tool MCP Server
- Summary
- FAQ
- Useful Sources
Picture this: it's 2 PM, you're deep in a debugging session, and you need to check a support ticket status, query the internal database, and post a Slack update - all at once. So you alt-tab to Jira, copy the ticket ID, open your DB client, paste a query, switch to Slack, type the message. Six tools. Four minutes. Zero flow state.
MCP eliminates all of that. With the Model Context Protocol, Claude Code talks directly to your internal tools - no copy-pasting, no context-switching. By the end of this guide, you'll have a working MCP integration connecting Claude Code to your own internal systems.
🔑 Key Takeaways
- MCP (Model Context Protocol) is an open standard by Anthropic that gives Claude a unified interface to external tools and data sources.
- Claude Code supports all three MCP capability types: Tools, Resources, and Prompts.
- You can connect Claude Code to databases, REST APIs, Slack, GitHub, and any internal system in under an hour.
- MCP servers are configured via the
claude mcp addCLI command or~/.claude.json- not.claude/settings.json.- Security is non-negotiable: use environment variables for credentials, apply least-privilege access, and log every tool invocation.
What Is MCP (And Why Should You Care)?
MCP - Model Context Protocol - is an open standard created by Anthropic that lets AI models like Claude connect to external tools, data sources, and workflows through a single, unified interface. Anthropic open-sourced it in November 2024, and it's since become the de facto standard for AI-tool integration, supported by VS Code, Cursor, ChatGPT, and dozens of other clients. (For the fundamentals, see what MCP is.)
The official docs put it well: "Think of MCP like a USB-C port for AI applications." Just as USB-C replaced a drawer full of incompatible cables, MCP replaces a tangle of bespoke integrations with one standard connector.
The Three Core Components
Every MCP setup has three moving parts:
- Host - the AI application running the show (e.g., Claude Code). It initiates connections and orchestrates everything.
- Client - the protocol layer inside the host that manages the actual communication with servers.
- Server - a lightweight process you build and run that exposes your tools, data, or prompts to Claude.
Transport Types
How the host and server communicate depends on where the server lives:
- stdio (Standard I/O) - for local servers running on the same machine. Fast, simple, zero network overhead. This is what you'll use for most development setups.
- HTTP + SSE (Server-Sent Events) - for remote servers. The client sends HTTP requests; the server streams responses via SSE.
- Streamable HTTP - the modern standard for remote MCP servers, introduced in 2025. More efficient than SSE for high-throughput scenarios.
For internal tool integration, stdio is usually the right call unless your MCP server needs to be shared across multiple machines or teams.
Claude Code + MCP: What's Possible?
Claude Code is Anthropic's agentic coding assistant - a CLI tool that understands your codebase, runs terminal commands, edits files, and now, via MCP, talks directly to your internal systems. (For how Cursor, Windsurf, and Claude Code compare, see our guide to agentic coding with MCP.)
Without MCP, Claude Code is powerful but isolated. It knows your code, but it can't reach your data. With a Claude Code MCP integration, the picture changes completely:
| Without MCP | With MCP |
|---|---|
| Manually copy ticket ID from Jira | Ask Claude: "What's the status of ticket TKT-4821?" |
| Open DB client, write and run query | Ask Claude: "Show me all orders from last week over $500" |
| Switch to Slack, type deployment message | Ask Claude: "Post a deploy notice to #releases" |
| Browse internal wiki in a browser | Ask Claude: "Summarize the onboarding docs for new engineers" |
| Trigger CI/CD pipeline from a dashboard | Ask Claude: "Kick off the staging deploy pipeline" |
| Create GitHub PR manually | Ask Claude: "Open a PR for this branch and assign it to @teamlead" |
The pattern is the same every time: you describe what you want in plain language, and Claude handles the tool call.
How MCP Works Under the Hood
MCP uses JSON-RPC 2.0 as its message format - a lightweight, stateless protocol where every message is a JSON object with a method, params, and an ID. If you've worked with language servers (LSP), the pattern will feel familiar.
The Three Capability Types
Your MCP server can expose three kinds of capabilities:
- Tools - executable functions Claude can call. Think
get_ticket_status,run_sql_query,post_slack_message. Each tool has a name, a description, and a typed input schema. - Resources - read-only data sources Claude can access. Think internal wiki pages, DB schemas, config files. Identified by URI (e.g.,
file://docs/onboarding.md). - Prompts - reusable prompt templates your team can standardize. Useful for things like "always format DB results as a markdown table."
(For a deeper look at the three primitives, see MCP tools vs resources vs prompts.)
The Lifecycle
Here's what happens every time Claude Code invokes a tool:
- Initialization - Claude Code starts your MCP server process and establishes a connection via the configured transport.
- Capability discovery - Claude asks the server: "What tools/resources/prompts do you have?" The server responds with a full manifest.
- Invocation - Claude sends a JSON-RPC request with the tool name and validated arguments.
- Execution - your server runs the actual logic (API call, DB query, etc.) and returns a structured response.
- Response - Claude receives the result and incorporates it into its reply to you.
The whole round-trip is typically under 200ms for local stdio servers. Remote HTTP servers add network latency, but for internal VPN-hosted tools that's usually still under a second.
Setting Up Your First MCP Server (Step-by-Step)
Let's build a real MCP server. We'll create one that fetches internal support ticket data - a dead-simple but genuinely useful example. (For a fuller walkthrough of building one from scratch, see our build an MCP server in Python guide.)
Prerequisites:
- Node.js 18+ (or Python 3.10+ if you prefer)
- Claude Code installed (
npm install -g @anthropic-ai/claude-code) - Basic TypeScript or Python knowledge
Step 1: Install the MCP SDK
# TypeScript / Node.js
npm install @modelcontextprotocol/sdk zod
# Python
pip install mcp
Add "type": "module" to your package.json - the SDK uses ES modules.
Step 2: Create a Basic MCP Server
Here's a complete, working TypeScript MCP server that exposes a get_ticket_status tool:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "internal-tools-server",
version: "1.0.0",
});
server.tool(
"get_ticket_status",
"Fetch the status of an internal support ticket",
{
ticket_id: z.string().describe("The ticket ID to look up"),
},
async ({ ticket_id }) => {
// Replace with your actual internal API call
const response = await fetch(
`https://internal.yourcompany.com/api/tickets/${ticket_id}`,
{
headers: {
Authorization: `Bearer ${process.env.INTERNAL_API_KEY}`,
},
}
);
if (!response.ok) {
return {
content: [
{
type: "text",
text: `Error: API returned ${response.status}`,
},
],
};
}
const data = await response.json();
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
A few things worth noting here. Zod handles input validation - if Claude passes a non-string ticket ID, it'll be caught before your handler runs. The process.env.INTERNAL_API_KEY pattern keeps credentials out of your source code. And the error handling on response.ok means Claude gets a useful message instead of a silent failure. (Getting tool names and descriptions right is what makes Claude pick the right tool reliably.)
Compile it:
npx tsc
Step 3: Configure Claude Code to Use Your MCP Server
Important: MCP server configs go in ~/.claude.json (user-level) or via the CLI - not in .claude/settings.json, which silently ignores MCP entries as of 2025.
The easiest way is the CLI:
claude mcp add internal-tools node ./dist/server.js
Or add it directly to ~/.claude.json:
{
"mcpServers": {
"internal-tools": {
"command": "node",
"args": ["./dist/server.js"],
"env": {
"INTERNAL_API_KEY": "your-api-key-here"
}
}
}
}
For project-scoped configs (great for team repos), you can also use a .mcp.json file in your project root - Claude Code picks this up automatically.
Step 4: Test the Connection
List your registered MCP servers to confirm Claude Code sees it:
claude mcp list
You should see internal-tools in the output. To verify the tool is available, start a Claude Code session and run /mcp - it shows all connected servers and their registered tools. You should see get_ticket_status listed under internal-tools.
Step 5: Use It in Claude Code
Now just ask:
> What's the status of ticket TKT-4821?
Claude Code will recognize that get_ticket_status is the right tool, call it with ticket_id: "TKT-4821", and return the result inline. No tab-switching required.
Real-World Use Cases: Connecting to Internal Tools
Here's where the MCP integration really pays off. These four patterns cover 80% of what most teams need.
Internal Database (PostgreSQL)
The archived @modelcontextprotocol/server-postgres reference server is a solid starting point. Add it to your config:
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-postgres",
"postgresql://readonly_user:password@internal-db:5432/mydb"
]
}
}
}
Key tip: Use a read-only database user for query tools. There's no reason Claude needs INSERT or DROP permissions for analytics queries.
Example prompt: "Show me all users who signed up in the last 7 days but haven't completed onboarding."
Internal REST API
The pattern for any internal REST API is the same: map each endpoint to a named tool with a clear, specific description. Claude uses the description to decide when to call it.
server.tool(
"get_deployment_status",
"Check the current deployment status for a given service in staging or production",
{
service: z.string().describe("Service name, e.g. 'auth-api' or 'payments'"),
env: z.enum(["staging", "production"]),
},
async ({ service, env }) => {
// your internal API call here
}
);
Vague descriptions like "get data" confuse Claude. Specific ones like "Check the current deployment status for a given service" work every time.
Slack / Team Messaging
The community-maintained Zencoder Slack MCP server is the current go-to after Anthropic archived the official one:
{
"mcpServers": {
"slack": {
"command": "npx",
"args": ["-y", "slack-mcp-server"],
"env": {
"SLACK_BOT_TOKEN": "xoxb-your-token",
"SLACK_TEAM_ID": "T0XXXXXXX"
}
}
}
}
Example prompt: "Post a message to #releases: 'v2.4.1 deployed to production at 14:32 UTC. All health checks passing.'"
GitHub / Internal Git
The Git reference server handles local repositories directly:
{
"mcpServers": {
"git": {
"command": "uvx",
"args": ["mcp-server-git", "--repository", "/path/to/your/repo"]
}
}
}
Example prompt: "Create a PR for the feature/auth-refactor branch, set the title to 'Refactor auth middleware', and assign it to @teamlead."
Security Best Practices
This section isn't optional. MCP gives Claude real access to real systems. Treat it accordingly.
Never hardcode credentials. Always use environment variables passed through the env block in your MCP config. Better yet, use a secrets manager like HashiCorp Vault and inject values at runtime.
Apply least privilege. Your MCP server should only have the permissions it actually needs. A ticket-reading tool doesn't need write access to your ticketing system. A DB query tool doesn't need DDL permissions.
Use read-only database users for any query-only MCP tools. Create a dedicated claude_readonly Postgres role with SELECT only.
Validate and sanitize all inputs inside your server handler - even though Zod catches type errors, you still need to guard against injection attacks in dynamic query construction.
For remote MCP servers: enforce HTTPS, implement OAuth 2.0 or short-lived API key rotation, and never expose your MCP server to the public internet without authentication.
Log every tool invocation. At minimum, log: timestamp, tool name, input arguments (redact sensitive fields), and the response status. This gives you an audit trail when something goes wrong.
Network isolation. Run MCP servers inside your VPN or private network. There's no reason an internal-tools server needs to be publicly reachable.
Rotate API keys regularly. Set a calendar reminder. Monthly is a reasonable baseline; weekly for high-privilege integrations.
Troubleshooting Common Issues
| Problem | Likely Cause | Fix |
|---|---|---|
| Claude doesn't see my MCP server | Config in wrong file (.claude/settings.json is ignored for MCP) |
Use claude mcp add CLI or edit ~/.claude.json directly |
| Tool call returns empty result | API key missing or wrong env var name | Run echo $INTERNAL_API_KEY in your shell; test the API call with curl independently |
| Connection timeout | Remote server unreachable or firewall blocking | Check firewall rules; verify the server URL with curl -v |
| "Tool not found" error | Tool name mismatch between registration and call | Run /mcp in Claude Code to see exact registered tool names |
| stdio transport hangs | Server process not exiting cleanly on error | Add process.on('uncaughtException', ...) and ensure all async paths return or throw |
| Server starts but tools don't appear | SDK version mismatch or missing await server.connect() |
Update @modelcontextprotocol/sdk to latest; confirm connect() is awaited |
The most common gotcha by far is the config file location. If Claude Code can't see your server, check ~/.claude.json first - not .claude/settings.json.
Advanced: Building a Multi-Tool MCP Server
Once your first tool works, adding more is trivial - just call server.tool() again. But there are a few architectural decisions worth making upfront.
Organize servers by domain. One server per internal system (one for your DB, one for Slack, one for your CI/CD platform) is easier to maintain, debug, and permission than one monolithic server with 30 tools.
Use Resources for data, Tools for actions. If Claude needs to read your internal API documentation or a DB schema, expose it as a Resource (identified by URI) rather than a Tool. Resources are passive - Claude can read them without "doing" anything.
server.resource(
"db-schema",
"postgres://internal-db/schema",
async (uri) => ({
contents: [{
uri: uri.toString(),
text: await getSchemaAsMarkdown(),
mimeType: "text/markdown",
}],
})
);
Use Prompts for team standards. If your team always wants DB results formatted a certain way, or always wants PR descriptions to follow a template, encode that as a Prompt. It becomes a reusable template Claude can invoke on demand.
Add proper error handling everywhere. Every tool handler should catch exceptions and return a structured error message rather than crashing the server. A crashed server means Claude loses all its tools mid-session.
Summary
🔑 Key Takeaways (Recap)
- MCP is the standard for connecting Claude Code to external tools - one protocol, any system.
- The three capability types are Tools (actions), Resources (data), and Prompts (templates).
- Configure MCP servers via
claude mcp addor~/.claude.json-.claude/settings.jsonwon't work for MCP.- Use Zod for input validation, environment variables for credentials, and read-only DB users for query tools.
- Log every tool invocation and run MCP servers inside your private network.
- Start with one tool, one server. Expand from there.
FAQ
What is MCP in Claude Code?
MCP (Model Context Protocol) is an open standard by Anthropic that lets Claude Code connect to external tools, databases, and APIs through a unified interface. In Claude Code, MCP enables you to register custom servers that expose callable tools - so Claude can query your database, post Slack messages, or call internal APIs directly from a chat session, without you switching tabs.
How do I add an MCP server to Claude Code?
Use the CLI command claude mcp add <server-name> <command>. For example: claude mcp add internal-tools node ./dist/server.js. Alternatively, edit ~/.claude.json directly and add an entry under "mcpServers". Avoid putting MCP configs in .claude/settings.json - that file doesn't support MCP server definitions as of 2025.
Can I connect Claude Code to a private internal API?
Yes. Build a custom MCP server that wraps your internal API endpoints as named tools. Pass credentials via environment variables in your MCP config's env block. Run the server inside your VPN or private network so it's never exposed publicly. Claude Code communicates with it locally via stdio transport.
Is MCP secure for internal tools?
It can be, if you follow the right practices. Never hardcode credentials - use environment variables or a secrets manager. Apply least-privilege permissions to every MCP server. Use read-only database users for query tools. Log all tool invocations with timestamps. For remote servers, enforce HTTPS and OAuth 2.0. The protocol itself is secure; the risk comes from misconfiguration.
What's the difference between stdio and HTTP transport in MCP?
stdio runs the MCP server as a local subprocess and communicates over standard input/output. It's fast, simple, and ideal for developer machines or single-user setups. HTTP transport (including the modern Streamable HTTP standard) runs the server as a network service, which is better for shared team infrastructure or cloud-hosted tools. For most internal tool integrations on a dev machine, stdio is the right default.
Does MCP work with Claude Code CLI?
Yes, fully. Claude Code CLI has first-class MCP support - it connects to MCP servers on startup, discovers available tools via capability negotiation, and invokes them automatically when relevant. Use claude mcp list to see registered servers and /mcp inside a session to inspect available tools. All three capability types (Tools, Resources, Prompts) are supported.
Useful Sources
Keep reading
How Cursor, Claude Code, and Windsurf Use MCP for Agentic Coding
Cursor, Claude Code, and Windsurf all support MCP - but they implement it differently. Setup, real workflows, and a side-by-side comparison.
The Three-Layer AI Agent Stack: MCP, A2A, and Streamable HTTP Explained
MCP, A2A, and Streamable HTTP are the three protocols that form the modern AI agent stack. Here's exactly how they fit together — and why it matters for every developer building with AI.
Best MCP Servers in 2026: GitHub, Notion, Google Drive, and More
There are over 9,600 MCP servers out there — but only a handful are worth your time. Here's a curated breakdown of the best MCP servers in 2026, with setup tips and real use cases.



