MCP server architecture
An MCP server is a process that speaks the MCP protocol. It exposes three kinds of capability to Claude:
Tools
Functions Claude can call — like API endpoints.
Resources
Data Claude can read — like file contents or database records.
Prompts
Pre-defined instruction templates invokable as slash commands.
The server communicates over stdio (local) or HTTP/SSE (remote). Claude Code connects to it through your .mcp.json or user-level MCP config.
A simple stdio server in Node.js
Here's a minimal server that exposes one tool — fetching a Jira issue by key. Register a tools/list handler so Claude knows what's available, and a tools/call handler to do the work.
// my-company-mcp/index.js
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server({
name: "my-company-tools",
version: "1.0.0"
}, {
capabilities: { tools: {}, resources: {}, prompts: {} }
});
// Register a tool
server.setRequestHandler("tools/call", async ({ params }) => {
if (params.name === "get_jira_issue") {
const issue = await jiraClient.getIssue(params.arguments.issue_key);
return {
content: [{ type: "text", text: JSON.stringify(issue) }]
};
}
});
// Register tool definitions
server.setRequestHandler("tools/list", async () => ({
tools: [{
name: "get_jira_issue",
description: "Fetch a Jira issue by key. Returns title, description, status, and comments.",
inputSchema: {
type: "object",
properties: {
issue_key: { type: "string", description: "Jira issue key, e.g. PROJ-123" }
},
required: ["issue_key"]
}
}]
}));
const transport = new StdioServerTransport();
await server.connect(transport);
Packaging as a Claude Code plugin
To distribute the server, wrap it in a plugin. A plugin bundles one or more MCP servers alongside skills and docs in a predictable layout:
my-company-plugin/
plugin.json # Plugin manifest
servers/
jira/
index.js # MCP server entry point
package.json
skills/
create-issue.md # Claude skill that uses the MCP tools
README.md
The manifest declares the server, its launch command, and any environment variables:
// plugin.json
{
"name": "my-company-tools",
"version": "1.0.0",
"description": "Internal tools for Acme Corp development workflow",
"mcpServers": {
"jira": {
"command": "node",
"args": ["servers/jira/index.js"],
"env": {
"JIRA_API_TOKEN": "${JIRA_API_TOKEN}",
"JIRA_DOMAIN": "${JIRA_DOMAIN}"
}
}
}
}
Resources and prompts
Beyond tools, expose resources Claude can @-reference and prompts that show up as slash commands. Register a list handler for each:
// Expose resources (data Claude can @-reference)
server.setRequestHandler("resources/list", async () => ({
resources: [{
uri: "jira://recent-issues",
name: "Recent Jira Issues",
description: "Issues created or updated in the last 7 days"
}]
}));
// Expose prompts (slash commands)
server.setRequestHandler("prompts/list", async () => ({
prompts: [{
name: "create-issue-from-bug",
description: "Create a Jira issue from a bug report",
arguments: [
{ name: "description", description: "Bug description", required: true }
]
}]
}));
Tips for good tool design
The model only knows what your descriptions tell it. Treat tool definitions like documentation written for a smart colleague who can't see your code.
- Write descriptions as if explaining to a non-technical colleague.
- Use concrete examples in descriptions — "e.g. PROJ-123".
- Keep tool scope narrow: one tool, one responsibility.
- Return structured data Claude can reason about, not just raw JSON.
- Add error messages that help Claude understand what went wrong and how to fix it.
Submitting to the marketplace
Ready to share publicly? The community marketplace path is straightforward:
Host the plugin on a public GitHub repository.
Follow the plugin structure exactly.
Add a comprehensive README with setup instructions.
Submit via the marketplace submission form in the official docs.
Plugins go through Anthropic security review before listing.
Practice project
Build an MCP server that connects Claude Code to your most-used work tool (Jira, Linear, Notion, PagerDuty, …). Expose two or three well-described tools, package the server as a Claude Code plugin, and share it with your team.
Once your server works, learn how to drive it programmatically with The Agent SDK, or revisit the full Claude Code tutorial series to see where MCP fits in the bigger picture.
Notes
Tool descriptions are your API docs
Claude picks tools from names and descriptions alone. Vague one-liners cause wrong tool calls; include parameter examples, id formats, and what the tool returns on failure.
Stdio servers must flush and exit cleanly
Debug console.log on stdout corrupts the JSON-RPC stream. Log to stderr only. Hung processes leave Claude Code with a zombie server until session restart.
Context bloat from too many tools
Every registered tool adds tokens to every turn. Split large integrations into focused servers and enable MCP tool search on the client when you exceed a dozen tools.
OAuth in CI needs pre-provisioned tokens
Remote HTTP servers with browser OAuth work interactively but break in headless CI. Store refresh tokens in secrets and document the rotation path before shipping a server to production pipelines.
Quick summary
- An MCP server exposes tools, resources, and prompts over stdio or HTTP/SSE
- A minimal Node.js server needs
tools/listandtools/callhandlers - Package it as a plugin with
plugin.jsonto distribute servers, skills, and docs together - Write descriptions for a non-technical reader, keep tools narrow, and submit to the marketplace via GitHub
Building MCP Servers FAQ
What is an MCP server?
An MCP server is a process that speaks the MCP protocol and exposes three kinds of capability to Claude: tools Claude can call, resources Claude can read, and prompts that show up as slash commands.
How do you build a custom MCP server for Claude Code?
Build a process that speaks the MCP protocol and registers handlers for the capabilities you want to expose. A minimal Node.js server needs a tools/list handler so Claude knows what's available and a tools/call handler to do the work.
How does an MCP server communicate with Claude Code?
The server communicates over stdio for local servers or HTTP/SSE for remote ones. A local stdio server in Node.js connects through a StdioServerTransport.
How do you package an MCP server as a Claude Code plugin?
Wrap the server in a plugin that bundles one or more MCP servers alongside skills and docs in a predictable layout. A plugin.json manifest declares each server, its launch command, and any environment variables.
How do you connect a custom MCP server to Claude Code?
Claude Code connects to the server through your .mcp.json or user-level MCP config. When packaged as a plugin, the plugin.json manifest declares the server and its launch command for Claude Code to start.