MCP Server
The component that exposes tools, resources, or prompts to AI clients through a controlled protocol boundary.
Server role
An MCP server turns a real system into a capability that an AI client can discover and call. The server owns integration details, validates inputs, performs the action, and returns a structured result.
Design a good capability
Use a verb-noun name such as search_docs, create_ticket, or validate_invoice.
Explain when the client should call it and what result it returns.
Make required fields explicit and avoid free-form blobs when structured fields are possible.
Return predictable data that the agent can use without parsing prose.
Copyable tool definition
This definition is intentionally framework-neutral. Real MCP SDK code can expose the same contract through protocol APIs.
export const searchDocsTool = {
name: "search_docs",
description: "Search indexed documentation pages for beginner questions about MCP and Skills.",
inputSchema: {
type: "object",
required: ["query"],
properties: {
query: {
type: "string",
minLength: 1,
description: "Search phrase from the user's question."
},
limit: {
type: "number",
minimum: 1,
maximum: 10,
default: 3
}
}
}
}Write the handler
The handler is the server-owned part. Keep validation close to execution and make the response shape stable.
export function handleSearchDocs(input, pages) {
const query = input?.query
const limit = input?.limit ?? 3
if (!query || typeof query !== "string") {
return capabilityError("INVALID_QUERY", "query must be a non-empty string", false)
}
if (!Number.isInteger(limit) || limit < 1 || limit > 10) {
return capabilityError("INVALID_LIMIT", "limit must be an integer from 1 to 10", false)
}
const q = query.toLowerCase()
return {
results: pages
.filter((page) => `${page.title} ${page.body}`.toLowerCase().includes(q))
.slice(0, limit)
.map(({ title, url, body }) => ({ title, url, snippet: body }))
}
}
function capabilityError(code, message, retryable) {
return { error: { code, message, retryable } }
}Error behavior
Errors are part of the API. A good server explains what failed and whether the client can retry, ask the user for missing information, or stop.
{
"error": {
"code": "MISSING_REQUIRED_FIELD",
"message": "The customer_id field is required.",
"retryable": false,
"user_action": "Ask the user for a customer id."
}
}Three errors beginners should test
handleSearchDocs({}, pages)
// INVALID_QUERY: the client should ask for a search phrase.
handleSearchDocs({ query: "MCP", limit: 99 }, pages)
// INVALID_LIMIT: the client should retry with a valid limit.
handleSearchDocs({ query: "billing" }, pages)
// Not an error. Return { "results": [] } so the agent can say nothing matched.