--- name: setup description: > Post-init orientation for an MCP server built on @cyanheads/mcp-ts-core. Use after running `@cyanheads/mcp-ts-core init` to understand the project structure, conventions, and skill sync model. Also use when onboarding to an existing project for the first time. metadata: author: cyanheads version: "1.12" audience: external type: workflow --- ## Context This skill assumes `bunx @cyanheads/mcp-ts-core init [name]` has already run. The CLI created the project's `CLAUDE.md` and `AGENTS.md` for different agents, copied external skills to `framework-skills/`, and scaffolded the directory structure with echo definitions as starting points. This skill covers what was created and what to do next. ## Agent Protocol File The init CLI generates both `CLAUDE.md` and `AGENTS.md` with identical content — `CLAUDE.md` is read by Claude Code, `AGENTS.md` by Codex, Cursor, Windsurf, and other agents. **Keep both.** Shipping both keeps the project agent-agnostic, and they're cheap to hold in sync: edit one, then `cp CLAUDE.md AGENTS.md` (the framework keeps its own pair byte-identical the same way, enforced by `check-docs-sync`). Only delete one if you're certain the project will never be opened by the other family of agents. For the full framework docs, read `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` (or its identical twin `AGENTS.md`) once per session. It contains the exports catalog, tool/resource/prompt contracts, error codes, context API, and common import patterns. ## Project Structure What `init` actually creates: ```text CLAUDE.md # Agent protocol — Claude Code AGENTS.md # Agent protocol — other agents (Codex, Cursor, etc.) package.json # Starter deps + scripts (placeholders substituted on init) tsconfig.json # Typecheck config — covers src/ and tests/, emits nothing tsconfig.build.json # Build config — emits src/ to dist/, tests excluded vitest.config.ts # Test runner config biome.json # Lint + format config devcheck.config.json # Which devcheck steps to run Dockerfile # Starter multi-stage image .dockerignore .env.example # Copy to .env and fill in .gitignore .github/ISSUE_TEMPLATE/ # Bug / feature-request issue forms .vscode/ # Recommended extensions + editor settings server.json # MCP Registry publishing metadata changelog/template.md # Format reference for per-version changelog files scripts/ # Framework scripts (build, devcheck and its checks, lint-mcp, lint-packaging, Docker install-otel and prune-musl-packages, release-github, …), re-synced by `maintenance` framework-skills/ # External skills copied from the package (source of truth) src/ index.ts # createApp() entry point mcp-server/ tools/definitions/ echo.tool.ts # Standard tool starter echo-app.app-tool.ts # UI-enabled app tool starter (pairs with echo-app-ui resource) resources/definitions/ echo.resource.ts # Standard resource starter echo-app-ui.app-resource.ts # UI resource paired with echo-app app tool prompts/definitions/ echo.prompt.ts # Prompt starter tests/ tools/echo.tool.test.ts # Starter tests (one per echo definition) resources/echo.resource.test.ts prompts/echo.prompt.test.ts smoke/definitions.smoke.test.ts # Every shipped definition executed once integration/echo-contract.int.test.ts # The echo tool driven through the production surfaces fuzz/echo-tool.fuzz.test.ts # Property-based coverage, via the fast-check dev dep ``` Add these as needed: ```text src/ worker.ts # createWorkerHandler() — only for Cloudflare Workers config/ server-config.ts # Server-specific env vars (own Zod schema) services/ [domain]/ [domain]-service.ts # Init/accessor pattern types.ts ``` ## Scaffolded Echo Definitions The init creates five echo definitions plus matching starter tests: | File | Demonstrates | |:--|:--| | `echo.tool.ts` | Standard MCP tool: input/output Zod schemas, `handler`, `format` | | `echo-app.app-tool.ts` | MCP App tool — same as a tool, but emits a UI (`ui_app://` link) for clients that render MCP Apps | | `echo.resource.ts` | Standard MCP resource with a parameterised URI template | | `echo-app-ui.app-resource.ts` | UI resource served to MCP App clients; paired with `echo-app.app-tool.ts` | | `echo.prompt.ts` | Prompt template (pure message generator) | | `tests/**/echo.*.test.ts` | Starter tests using `createMockContext` — edit alongside the definitions | After init: 1. **Clean up what you don't need.** If your server has no prompts, delete the echo prompt and its registration in `src/index.ts`. Same for resources, or the app-tool pair if you're not targeting UI-capable clients. 2. **Rename and replace what you keep.** The echo definitions and their tests show the pattern — swap them out for your real tools/resources/prompts. 3. **Definitions register directly in `src/index.ts`.** The init scaffold uses direct imports — no barrel files yet. As the definition count grows, the `add-tool`/`add-resource`/`add-prompt` skills introduce `definitions/index.ts` barrels per the framework convention. See the `add-tool`, `add-app-tool`, `add-resource`, `add-prompt`, `add-service`, and `add-test` skills for the scaffolding patterns when you start adding real definitions. ## Conventions | Convention | Rule | |:-----------|:-----| | File names | kebab-case | | Tool/resource/prompt names | snake_case, prefixed with server name (e.g. `tasks_fetch_list`) | | File suffixes | `.tool.ts`, `.resource.ts`, `.prompt.ts`, `.app-tool.ts` (UI-enabled), `.app-resource.ts` (paired UI resource) | | Imports (framework) | `@cyanheads/mcp-ts-core` and subpaths | | Imports (server code) | `@/` path alias for `src/` | ## Skill Sync Copy all project skills into your agent's skill directory so they're available as context. `framework-skills/` is the source of truth. It is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, and these are development skills, not skills for the agents that install the server — leave `skills/` for those. **Don't edit `framework-skills/*/SKILL.md` or `framework-skills/*/references/*`.** These are external skill files synced from `@cyanheads/mcp-ts-core` — the `maintenance` skill overwrites them on package updates, so local edits get lost. Project-specific agent context belongs in `CLAUDE.md` / `AGENTS.md`. **For Claude Code:** ```bash mkdir -p .claude/skills && cp -R framework-skills/* .claude/skills/ ``` **For other agents** (Codex, Cursor, Windsurf, etc.) — copy to the equivalent directory (e.g., `.codex/skills/`, `.cursor/skills/`). This step is the **bootstrap** — it creates the agent directory. From then on, use the `maintenance` skill to refresh it after package updates (Phase B). Maintenance only refreshes directories that already exist; it won't create a new agent directory on your behalf. ## Project Scaffolding Complete these one-time setup tasks: 1. **Install dependencies** — `bun install` 2. **Update dependencies to latest** — `bun update --latest`. The scaffolded `package.json` pins minimum versions from when the framework was published; updating ensures you start with the latest compatible releases. 3. **Initialize git** — use your git tools: init the repo, stage all files, and commit with message `chore: scaffold from @cyanheads/mcp-ts-core` 4. **Verify the substituted server name** — when `init` runs without a `[name]` argument, the package name defaults to the cwd directory name. If that's not what you want as the published server name, update `package.json`, `CLAUDE.md`/`AGENTS.md`, and `server.json` to your actual server name. 5. **Populate the publishing identity** — the scaffold ships identity fields empty on purpose, so the first `devcheck` run is a to-do list rather than a green light. These fail the gate until they are set: | File | Gated fields | Gate | |:--|:--|:--| | `server.json` | `name` (reverse-DNS, e.g. `io.github./`), `description`, `repository.url` | `lint:mcp` | | `.claude-plugin/plugin.json` | `description` | `lint:packaging` | | `.codex-plugin/plugin.json` | `description`, `interface.shortDescription`, `interface.longDescription` | `lint:packaging` | Fill the rest of the same blocks while you are in them — `package.json` `description` and `repository.url`, both plugin manifests' `author` / `homepage` / `repository` / `keywords`, the Codex manifest's `interface.developerName` / `category` / `websiteURL`, and `manifest.json` `description` / `author.name`. Nothing gates them, and every install surface reads them. When the server takes a user-supplied value (an API key, a contact email, an instance URL), wire it into the plugin manifests the way each client delivers it — never as `"KEY": ""` in `env`, which `lint:packaging` rejects because the empty value replaces the user's exported key and is read as unset. In `.claude-plugin/plugin.json`, declare the option under `userConfig` (`type`, `title`, `description`; `sensitive: true` for keys and tokens; `required: true` or `default: ""`) and set `"KEY": "${user_config.