mcp-builder
Design and build Model Context Protocol servers that models use correctly - choosing stdio vs stateless Streamable HTTP while still serving 2025-era clients, shaping a few task-level tools with clear descriptions, JSON Schema inputs and honest annotations, bounding and paginating output, returning recoverable errors as isError results, securing remote servers with OAuth 2.1, and testing with the MCP Inspector. Use when someone asks "build an MCP server for our API", "turn this service into MCP tools", "why does the model keep calling the wrong tool", "add OAuth to my remote MCP server", or "my stdio server keeps disconnecting". Do NOT use for designing a REST or GraphQL API consumed by human-written code - use api-design instead - or for coordinating multiple LLM agents - use agent-orchestration instead.
- License 许可
- MIT
- 最近更新
- 2026-10-14
- Stars
- ★ 25
- Forks
- 3
01 前言 Front Matter
- name
- mcp-builder
- metadata
- { "title": "MCP Server Builder" }
- description
- Design and build Model Context Protocol servers that models use correctly - choosing stdio vs stateless Streamable HTTP while still serving 2025-era clients, shaping a few task-level tools with clear descriptions, JSON Schema inputs and honest annotations, bounding and paginating output, returning recoverable errors as isError results, securing remote servers with OAuth 2.1, and testing with the MCP Inspector. Use when someone asks "build an MCP server for our API", "turn this service into MCP tools", "why does the model keep calling the wrong tool", "add OAuth to my remote MCP server", or "my stdio server keeps disconnecting". Do NOT use for designing a REST or GraphQL API consumed by human-written code - use api-design instead - or for coordinating multiple LLM agents - use agent-orchestration instead.
03 SKILL.md 正文
MCP Server Builder
An MCP server is an API whose caller is a language model. It picks tools by name and description, pays in context for every byte returned, and cannot read your docs. The costly mistake this skill prevents is wrapping a REST API one endpoint per tool. That produces forty thin tools, vague descriptions, unbounded JSON and errors thrown as protocol failures, and the result is a server the model misuses, that floods the context window, and that fails silently.
Operating procedure
Step 1: Write the task list before the tool list
List the 5-10 concrete things users will ask for, such as "find open bugs assigned to me". Each task should take one or two calls. One search_issues with filters beats three list-and-filter tools; chain upstream calls inside the server.
Step 2: Choose the transport and protocol posture
- stdio is for a local subprocess (filesystem, git, CLIs). On stdio, stdout belongs to the protocol: log to stderr, never
console.logorprint. - Streamable HTTP is for remote or shared servers. Since spec revision 2026-07-28 the protocol is stateless:
- There are no sessions, no
Mcp-Session-Idand noinitializehandshake. Each request carries its protocol version and client capabilities in_meta. - Servers MUST implement
server/discoverto advertise their versions, capabilities and identity. Change notifications go oversubscriptions/listen. - Put cross-call state in server-minted handles that are passed back as ordinary tool arguments, not in connection state.
- There are no sessions, no
- Many deployed clients still speak 2025-era revisions, which use
initializeand sessions, so serve both eras. Use an SDK entry point that negotiates the era rather than hand-rolling the wire format. - The old HTTP+SSE transport is deprecated, so do not build on it.
Step 3: Pick the right primitive
- Tools are actions or queries the model invokes on its own.
- Resources are addressable, read-only context that the app or user attaches.
- Prompts are templates the user picks, often shown as slash commands.
Do not adopt the deprecated Roots, Sampling or Logging features in new servers. Instead, take paths as tool parameters, call your LLM provider directly, and log to stderr or OpenTelemetry. If a call needs more input from the user mid-flight, use the multi round-trip pattern. The server returns an input-required result (resultType: "input_required") and the client retries the request with the answers. The server never sends its own requests to the client.
Step 4: Design each tool's contract
- Name it
verb_nounin snake_case, unambiguous across servers (search_issues, notsearch). - Description: the first sentence says what the tool returns and when to use it. Then constraints, next calls, and when not to use it.
- Input schema: JSON Schema with types, enums for closed sets, defaults, and a description on every property. Accept human identifiers like
PROJ-123, not internal IDs the model cannot know. - Annotations: set
readOnlyHint,destructiveHint,idempotentHintandopenWorldHinthonestly, plus atitle. They drive the client's confirmation UX but are not a security boundary. - Output: concise text the model can reason over. Add
outputSchemaandstructuredContentwhen machine consumers need them. - Order: return the tool list in a deterministic order so client and prompt caches hit.
Step 5: Bound every response
- Cap lists at a default of 20 and a hard max of 100, and return an opaque cursor for the next page.
- Truncate long fields and say so, for example: "truncated; call get_issue for the full text".
- Budget in UTF-8 bytes, not items or JS string length, because one item can be 500 KB. Cut on a character boundary.
Step 6: Handle errors in the right layer
- Tool execution errors return a result with
isError: trueand an actionable message: what failed and what to try next. This covers an upstream 404, a rate limit, or bad arguments the model can fix. The TypeScript SDK v2 already returns schema-validation failures this way. - Protocol errors are JSON-RPC errors such as unknown method, malformed request or
UnsupportedProtocolVersionError. The model often never sees them.
Step 7: Secure remote servers
- Act as an OAuth 2.1 resource server. Unauthenticated requests get
401withWWW-Authenticate: Bearer resource_metadata="https://host/.well-known/oauth-protected-resource". - Serve protected resource metadata (RFC 9728) that names your authorization server. PKCE is mandatory. Clients should register with Client ID Metadata Documents; Dynamic Client Registration is deprecated and kept only for compatibility.
- Check that every token's audience is your server. Never pass the client's token through to upstream APIs.
- Validate the
Originheader on every Streamable HTTP server. Bind local servers to127.0.0.1.
Step 8: Test, then version
- Run the MCP Inspector (
npx @modelcontextprotocol/inspector <server command>) on every tool: valid, invalid, empty, last page, upstream outage. Test a current and a 2025-era client. - Try real prompts: if the model picks the wrong tool, fix the description.
- Tool names and schemas are a public API: add optional fields freely, never repurpose a name or change semantics.
Worked example - TypeScript SDK v2, stdio
import { McpServer } from "@modelcontextprotocol/server";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";
const MAX_BYTES = 24_000;
// Cut at a code-point boundary once the UTF-8 byte budget is spent.
function clip(text: string): string {
let bytes = 0, end = 0;
for (const ch of text) {
bytes += Buffer.byteLength(ch);
if (bytes > MAX_BYTES) return text.slice(0, end) + "\n[truncated - narrow the query]";
end += ch.length;
}
return text;
}
function buildServer() {
const server = new McpServer({ name: "issues", version: "1.2.0" });
server.registerTool(
"search_issues",
{
title: "Search issues",
description:
"Returns up to `limit` issues (key, title, status, assignee) matching a query; use it to find issues before reading or updating one. Paged via `cursor`; use get_issue for full text.",
inputSchema: z.object({
query: z.string().min(1).describe("Free-text search over title and body"),
status: z.enum(["open", "closed", "any"]).default("open"),
limit: z.number().int().min(1).max(100).default(20),
cursor: z.string().optional().describe("Opaque cursor from a previous call"),
}),
annotations: { readOnlyHint: true, openWorldHint: true },
},
async ({ query, status, limit, cursor }) => {
try {
const page = await tracker.search({ query, status, limit, cursor }); // your API client
let text = page.items
.map((i) => `${i.key} [${i.status}] ${i.title} (${i.assignee ?? "unassigned"})`)
.join("\n") || "No matches. Try a broader query or status=any.";
text = clip(text);
if (page.nextCursor) text += `\nMore: cursor=${page.nextCursor}`;
return { content: [{ type: "text", text }] };
} catch (err) {
return { isError: true, content: [{ type: "text", text: `Search failed: ${safeMessage(err)}. Retry shortly if rate-limited.` }] };
}
}
);
return server;
}
serveStdio(buildServer); // serves current and 2025-era clients by default
For HTTP, pass the same factory to createMcpHandler. The maintenance-only v1.x SDK (@modelcontextprotocol/sdk) speaks only the 2025 era. For Python (FastMCP), check which revisions your version supports.
Thresholds
- Aim for 5-15 tools per server. Above about 20, tool-selection accuracy tends to drop.
- Description first sentence: under ~200 characters, self-sufficient.
- Page size: default 20, max 100. Budget about 25 KB of text per response.
- Return within ~30 s; longer work needs progress notifications or start/poll tools.
- Every tool has annotations, and every destructive tool sets
destructiveHint: true.
Deliverable
A task-to-tool map, transport and era posture with reasons, tool definitions, bounded paginated outputs, an error contract, remote auth setup, Inspector results for both eras, and a README of tools and env vars.
Do NOT
- Do not mirror REST endpoints one to one.
- Do not keep per-connection state on the current protocol revision.
- Do not throw for recoverable failures or return unbounded payloads.
- Do not treat annotations as authorization or pass client tokens upstream.
- Do not drop 2025-era clients without a deliberate decision.
- Do not guess SDK method names; check the installed version's types.
Quality bar
- An unprompted model picks the right tool for every task on the list.
- Responses fit the byte budget, truncation is announced, and every failure message is actionable.
- Both protocol eras pass the Inspector checklist.
- The remote server returns a spec-correct 401 challenge and rejects tokens for the wrong audience or Origin.
04 同仓库更多 Skills
zone-2-cardio-plan
Use when someone asks to build a steady-state cardio plan, train an aerobic base or Zone 2, structure easy or endurance weekly cardio, train by heart-rate zones, or improve resting heart rate, VO2, or endurance. Builds a heart-rate-zone aerobic training block with session structure, modality choice, and progression. General fitness guidance, not medical advice. Do NOT use to design a strength or lifting program (use Strength Training Plan), high-intensity interval conditioning, HIIT, or circuits (use Conditioning & HIIT Program), or a mobility/flexibility routine (use Mobility Routine).
youtube-script-writer
Writes retention-engineered long-form YouTube scripts on a hook/body/payoff architecture - a first-30-seconds hook that survives the retention cliff, a beat-sheet body with mini-hooks and bridges, timed pattern interrupts, and a payoff that over-delivers - with B-roll and editing cues marked inline. Use when someone says "script my next video", "my retention graph dies at the start", "tighten this draft", or "outline a video on X". Do NOT use for the 3-second opener of TikTok, Reels, or Shorts - use video-hook-writer instead; do NOT use for spoken voiceover synced to on-screen tutorial actions - use narration-script instead; do NOT use for a sales demo built on a buyer's discovery notes - use demo-script instead; do NOT use to design the thumbnail and title - use thumbnail-concept instead.
win-back-campaign
Designs segmented win-back email sequences for dormant users - dormancy tiers, a three-beat sequence with a single proportional incentive, and a suppression rule - and measures success by re-activation, not opens. Use when someone says "our users went quiet", "design a win-back campaign", "how do I re-engage lapsed customers", "should we email people who haven't logged in for months", or lifecycle messaging has stopped landing with a lapsed segment. Do NOT use for recovering an unfinished checkout - use abandoned-cart-sequence instead. Do NOT use for diagnosing and fixing why active customers cancel - use churn-reduction instead. Do NOT use for building always-on onboarding or nurture flows - use email-drip-builder instead.
wikipedia-style
Writes neutral, verifiable, encyclopedic prose following Wikipedia's core content policies and manual of style - NPOV, inline citations, notability-backed claims, and a strict ban on promotional language. Use when someone asks "write this like a Wikipedia article", "draft a Wikipedia page for this company or person", "make this neutral and encyclopedic", or "why does my article draft keep getting rejected". Do NOT use for persuasive or opinion writing - use op-ed-writer instead; for thesis-driven academic papers, use academic-essay; for verifying claims in existing text, use fact-checker.
whitepapers
Structures and writes authoritative B2B whitepapers - a standalone executive summary, a data-sized problem statement, methodology, evidence-driven findings, recommendations, and a soft call to action - flagging every claim that needs a real citation. Use when a marketer asks "write a whitepaper on this topic", "turn our research into a lead-gen asset", "make this credible enough for executives", or a draft reads like a product pitch instead of analysis. Do NOT use for customer-story-driven proof - use case-study-builder instead; for policy audiences, use policy-brief.
white-space-analysis
Finds unmet market gaps by ranking jobs-to-be-done on importance versus satisfaction, overlaying them against current solutions and workarounds, and validating that each gap is real, winnable, and worth winning. Use when someone asks "where are the gaps in this market", "what's underserved here", "where should we play next", or is hunting for product, feature, or market-entry opportunities. Do NOT use for profiling competitors and building battle cards - use competitive-intelligence instead; for putting a dollar figure on an identified opportunity - use market-sizing instead; for extracting jobs-to-be-done from raw customer interviews - use jtbd-extractor instead.