Skip to content
Threadbare
MCP · · 5 min read

Better MCP skills

Three MCP skills, one Unsplash prompt, plan mode — here's what held up.

One of the bigger roadblocks for building high-quality MCP servers is a lack of skills that control the chaos. Skills are the guardrails between a REST-wrapper generator and an AI-native MCP server. So I ran three public skills against the same Unsplash prompt — plan mode, one shot, no iteration — and graded what came back. The grades reflect plan quality plus first-pass implementation, not a week of polish.

SkillGradeDual transportAuthWorkflowsResponse shaping
MCP BuilderBYes (HTTP auth broken in code)Per-call headers in plan; broken in code1:1 RESTNone
MCP App BuilderC-STDIO hallucinatedaccessKey tool paramPartialNone
Workflow DesignA-YesHeaders + multi-tenantYesNone

Anthropic’s MCP Builder

Verdict: Solid B. Clean repo structure, extensive error handling, and a plan that nailed dual-transport with Client-ID and Authorization headers for HTTP and UNSPLASH_ACCESS_KEY for STDIO. Paired with a medium reasoning model, consider me impressed — with more direction or another plan iteration this would have been awesome.

Standout: Dependency-injection tool registration and thorough error handling. The agent also randomly added JSON and Markdown response modes; markdown is genuinely more model-friendly.

Dealbreaker: express and axios (sorry, not sorry — graduate to ky or native fetch), 1:1 mapping of the entire OpenAPI spec including write tools like unsplash_add_photo_to_collection, and HTTP auth that doesn’t actually work in the generated code.

MCP Use’s MCP App Builder

Verdict: C-. The agent didn’t implement STDIO support properly. The code is well-organized for a single-file dump, but sloppy types and broken STDIO does not instill confidence.

Standout: Native fetch with minimal glue — no axios dependency.

Dealbreaker: STDIO did not work at all. mcp-use doesn’t appear to support it out of the box, and Sonnet invented a connection path that doesn’t exist:

const isStdio = process.argv.includes("--stdio") || !process.stdin.isTTY;
if (isStdio) {
const transport = new StdioServerTransport();
// What sonnet wrote 👇
// const sessionServer = server.getServerForSession("stdio");
// await sessionServer.connect(transport);
// the fix 👇
await server.nativeServer.connect(transport);
} else {
await server.listen();
}

Note: maybe there’s a better way with this library. If you know and reading this make you cringe, don’t hate me. I relied entirely on IDE intellisense, viewed zero docs, and did not use a LLM to figure this out.

Also dealbreaking: accessKey as a tool param rather than a header (security risk if keys get logged), and unknown[] when the API has typed responses 😅

const data = await fetchUnsplash<unknown[]>(
"/photos",
{ page, per_page: perPage, order_by: orderBy },
key,
);

MCP Workflow Design

I’m not biased here, I genuinely think this is a great skill 😉. Built alongside MCP OpenAPI Typescript Stack to enforce the patterns from Building MCPs is not hard — typed SDK, dual transport without defaulting to a full web framework, tenancy questions upfront, and workflow tools instead of 1:1 REST.

Verdict: A-. Workflows, multi-tenant auth, and repo structure beat the field. The shared response-filtering gap keeps it from a straight A.

Standout: Multi-tenant HTTP auth with Client-ID, Authorization, X-Unsplash-ID, and X-Unsplash-Secret headers. Workflow tools for managing collections and photos, fanning out with Promise.allSettled and partial failure handling so one upstream miss doesn’t kill the whole response.

Dealbreaker: Same as the others — JSON mode dumps the complete API response with no field-level trimming. The plan also skipped asking about tool selection or the intent of the MCP server before generating.

What every skill missed

Three failures showed up everywhere, regardless of grade:

  • No response shaping. Every skill returned full JSON payloads — exif, blur_hash, current_user_collections, the works. Field-level trimming is table stakes; none of the skills enforced it.
  • No intent clarification. None of the agents asked what the MCP server is actually for before mapping endpoints. That leads to write tools nobody asked for and REST wrappers instead of curated APIs.
  • Auth design gaps. MCP Builder planned per-call headers but shipped broken HTTP auth. MCP App Builder put credentials in tool params. Only Workflow Design got auth right end-to-end.

What a good MCP skill should enforce

From this bake-off, a skill worth installing should push the agent to:

  1. Ask about transport and tenancy before generating code — stdio-only vs multi-tenant HTTP is not a detail you fix later.
  2. Prefer SDK transport over express-by-default. The TypeScript MCP SDK ships with WebStandardStreamableHTTPServerTransport; reach for Hono or Elysia only when you actually need middleware.
  3. Composite workflow tools over 1:1 OpenAPI mapping. Agents are great at mirroring REST. Skills should steer toward outcome-oriented tools that combine calls and return summaries.
  4. Per-request auth via headers or context, never as tool params. Credentials in tool arguments get logged.
  5. Normalize responses before returning to the model. Trim cruft, add a summary field when it helps, handle partial failures gracefully.

The skills I wrote to address this live in mcp-skills — specifically mcp-workflow-design and mcp-openapi-typescript-stack.


The skill isn’t teaching the agent to wire transport. It’s teaching the agent to ask what you actually want before generating forty REST wrappers.

If you want to see the full approach in the wild, the reference implementation is unsplash-mcp.