Writing your own MCP server
There are official SDKs in several languages for writing your own server. The SDK handles the JSON-RPC exchange, the transport and the protocol details; your job is defining the tools.
Each tool needs three things:
1. A name — a unique identifier.
2. A description — the only explanation the model ever sees.
3. An input schema — JSON Schema; parameter types and which are required.
Then the tool's actual implementation: it makes the request and returns the result.
| Decision | Recommendation | Reason |
|---|---|---|
| Tool granularity | One tool, one clear job | A universal tool with 20 parameters confuses the model |
| Result size | Filter and summarise | Results enter the context — 5 MB of JSON eats the budget |
| Error handling | Return a readable message | The model can read the error and adapt |
| Write operations | A separate, explicitly named tool | "I read" and "I changed" must not blur |
| Permissions | Least privilege in the server itself | The server's own permissions, not the agent's, set the real boundary |
// Conceptual shape — see the official SDK docs for exact API names.
// The points that matter: the description, the schema, the result size.
const GET_BUILD_STATUS = {
name: "get_build_status",
// This is ALL the model sees — what it does, what it returns, when to use it
description:
"Returns the status of the latest build for a branch from the CI system: " +
"result (success/failure), duration, and the names of failing tests. " +
"Use when the user asks about build status or investigates a CI failure.",
inputSchema: {
type: "object",
properties: {
branch: {
type: "string",
description: "Branch name, for example 'main' or 'feature/orders'",
},
},
required: ["branch"],
},
};
async function handleGetBuildStatus({ branch }) {
const res = await ci.getLatestBuild(branch);
// IMPORTANT: do not return the whole response. The result enters the context.
// Return only what the agent needs in order to decide.
return {
status: res.status,
durationSeconds: res.duration,
failedTests: res.failures?.slice(0, 20).map((f) => f.name) ?? [],
totalFailures: res.failures?.length ?? 0,
url: res.webUrl,
};
}The essential parts of a tool definition. The most common mistake is not filtering the result: returning the service's full JSON response can eat the context budget in one call.
Plan to test with the MCP Inspector before wiring anything up: it is the official debugging tool and lets you exercise a server without an AI client. Whether the tool list is right, the schema correct, the result in the expected shape — all of that can be checked before connecting to Claude Code.
Areas worth writing a server for on an Android team:
- An internal feature-flag system — which flags exist, their values, who changed them.
- Internal API documentation — the backend's real schema (so the agent reads it rather than guessing).
- The release/deployment system — which version is on which channel (reading; writing is a separate decision).
- The internal design system — tokens, component rules.
What they share: this information is not in the model's training data and gets copied by hand every time.
📚 Sources and documentation
- Build an MCP serverofficialmodelcontextprotocol.io
The official guide to SDKs, defining tools and running a server.
- MCP reference serversofficialgithub.com
Official example implementations — a good reference for tool design.