Example
MCP Server Example
A copyable beginner example for designing, implementing, and testing one MCP-style capability.
Goal
Build a small documentation search capability named search_docs. It accepts a query and returns matching pages with title, URL, and snippet.
Capability manifest
Start by writing the contract. A real MCP server would expose this information through the protocol; this simplified example keeps it readable.
{
"name": "search_docs",
"description": "Search documentation pages and return relevant results.",
"inputSchema": {
"type": "object",
"required": ["query"],
"properties": {
"query": { "type": "string", "description": "Search phrase." },
"limit": { "type": "number", "description": "Maximum result count." }
}
},
"outputSchema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": { "type": "string" },
"url": { "type": "string" },
"snippet": { "type": "string" }
}
}
}
}
}
}Capability handler
This is intentionally plain JavaScript so beginners can copy it and understand the behavior before wiring it into any server framework.
// search-docs.js
const pages = [
{
title: "MCP Architecture",
url: "/mcp/architecture",
body: "Clients discover servers. Servers expose tools, resources, and prompts."
},
{
title: "MCP Server",
url: "/mcp/server",
body: "A server validates input, executes capabilities, and returns structured results."
},
{
title: "Skill Structure",
url: "/skill/structure",
body: "A Skill contains SKILL.md plus optional references, scripts, and assets."
}
]
export function searchDocs(input) {
const query = input?.query
const limit = input?.limit ?? 5
if (!query || typeof query !== "string") {
return {
error: {
code: "INVALID_INPUT",
message: "query is required and must be a string",
retryable: false
}
}
}
const normalized = query.toLowerCase()
const results = pages
.filter((page) => `${page.title} ${page.body}`.toLowerCase().includes(normalized))
.slice(0, limit)
.map((page) => ({
title: page.title,
url: page.url,
snippet: page.body
}))
return { results }
}Test the handler
// test-search-docs.js
import { searchDocs } from "./search-docs.js"
console.log("success case")
console.log(JSON.stringify(searchDocs({ query: "server", limit: 2 }), null, 2))
console.log("error case")
console.log(JSON.stringify(searchDocs({ query: "" }), null, 2))What this teaches
DiscoveryThe client needs a name, description, and schema before the model can choose the tool.
ValidationThe server rejects bad input before touching real systems.
Structured resultThe agent receives fields it can use directly.
Error designThe error tells the client whether retrying makes sense.