Documents a portable TypeScript MCP server pattern: OpenAPI-generated Ky client + Zod request schemas, dual transports (stdio and streamable HTTP), optional multi-tenant credentials via AsyncLocalStorage, library-first public API with optional CLI, and authentication modeling (e.g. HTTP Basic from two-part secrets, Bearer/OAuth tokens, APIs that accept both). Use when scaffolding or refactoring any MCP server that wraps a REST API with @hey-api/openapi-ts (or similar codegen), when comparing this layout to FastMCP or other stacks, or after mcp-builder when the stack is TypeScript + generated SDK + MCP SDK, or when configuring or debugging @hey-api/openapi-ts (plugins, CLI, Zod validators, Ky client), or when adding opt-in env-gated debug logs for HTTP credential / tenant resolution.
This skill describes a reusable architecture pattern: a single package that can be an npm library, a CLI MCP process, and a thin MCP tool layer over a generated HTTP client. Reference implementations may use different file and function names; the roles stay the same.
Use it together with:
Do not duplicate those skills here. This document answers: how is the code organized, why, and what to decide before adopting the pattern on a greenfield or existing MCP server.
For the generated REST client and any hand-written fetch layer in this pattern,
use ky via @hey-api/client-ky.
Do not use axios — avoid @hey-api/client-axios, axios interceptors, or
axios examples when implementing or regenerating clients for MCP servers in this
stack. Ky fits Request/Response, keeps a small surface, and matches the
interceptor style used for per-request / multi-tenant auth.
What it is: @hey-api/openapi-ts is the CLI and defineConfig() entrypoint
that generates client.gen.ts (Ky), sdk.gen.ts (per-operation
functions), types, and optional Zod request schemas (zod.gen.ts) from
OpenAPI 3.x.
Run codegen from the repo root (after input spec is valid):
bunx openapi-ts
# or: npx @hey-api/openapi-ts
Typical openapi-ts.config.ts plugin chain for this stack: @hey-api/client-ky
(with baseUrl) → @hey-api/schemas → @hey-api/transformers →
@hey-api/typescript → @hey-api/sdk with validator: { request: "zod" } so MCP
atomic tools can reuse generated Zod for tool inputSchema. Plugin order
matters; the legacy plugin name @hey-api/services is now @hey-api/sdk
(output sdk.gen.ts, not services.gen.ts).
Authoritative detail (CLI flags, all plugins, migrations, Valibot): see reference/openapi-ts.md and the upstream hey-api/openapi-ts docs.
Answer these for any MCP server so the layout stays appropriate:
Tenancy / credentials
Transports
connect paths)?Deployment target
zod.gen.ts) can be thousands of lines; static
imports cause all schemas to evaluate at startup, exceeding the budget.
Plan for deferred module loading (see
reference/structure-and-flows.md).Distribution shape
create*McpServer in another app)?package.json exports + bin)?API surface
Workflow vs atomic tools
src/tools/<domain>.ts (or similar) and composite tools under
src/tools/workflows/.Upstream authentication schemes
Basic per vendor docs), Bearer (OAuth or PAT), both, or other
schemes (API-key headers, mTLS)?Optional follow-ups: Node vs Bun for the default HTTP entrypoint, rate limits, and whether tenant secrets may appear in process memory (TLS, resolver to KMS/DB, etc.).
Upstream REST APIs often document multiple valid ways to authenticate. The
MCP layer should model which modes you support in this package (documented in
README / env), and map each mode to one outbound Authorization (or custom)
header on the shared HTTP client — via default config, per-request ALS +
interceptor, or both.
1. HTTP Basic with a derived token (two-part secrets) Some vendors issue an access key (or ID) and a separate secret. The wire format is still RFC 7617 Basic: concatenate with a colon, Base64-encode the UTF-8 string, send:
Authorization: Basic <Base64(accessKey + ":" + accessKeySecret)>
Operators may create this once in a shell or your CLI reads two env vars and builds the header at startup. Do not confuse this with “username only” Basic (some APIs use empty password).
2. Bearer token (OAuth or non-OAuth PAT) After the user or system obtains a token (OAuth flow, developer portal, etc.),