# Pathrule Pattern: MCP Server Authoring (1.0.0)
# ::pathrule:package:mcp-server-authoring

### [RULE] Treat the tool description and schema as the LLM-facing API contract  (path: /src/mcp)
<!-- scope: folder | priority: high | strict -->

The model never sees your handler code. It decides whether and how to call a tool from the name, description, and input schema alone. Those three are the entire API as far as the LLM is concerned.

- Name tools by action and object (`create_issue`, `search_docs`), not by internal function names. The name is the first thing the model matches against intent.
- Write the description for the model: what the tool does, when to use it, when NOT to use it, and what it returns. Mention units, formats, and constraints the model would otherwise guess wrong. A vague description is the most common reason a tool is never called or called wrong.
- Declare an input schema for every tool (Zod or JSON Schema) with `.describe()` on non-obvious fields. The SDK validates input against it before your handler runs, so the handler receives typed, checked arguments - never raw, unvalidated input.
- Return structured, model-readable results and use the protocol's error channel (`isError`) for failures instead of throwing opaque strings. The model uses the result to decide its next step, so make the result legible.

---

### [RULE] Keep tools side-effect-honest and gate destructive actions  (path: /src/mcp)
<!-- scope: folder | priority: high | strict -->

A model will call any tool it is given if the description fits the goal. The server, not the model's good judgement, is what stops a destructive call from doing damage.

- A tool must do exactly what its description says and nothing more. A `search_*` tool must not write; a tool that mutates state must say so plainly in its description so the model (and the human approving it) understands the consequence.
- Gate destructive or irreversible actions (delete, send, charge, deploy): require an explicit `confirm: true` argument, or support a `dryRun` that returns what would happen without doing it. Do not let one unqualified call wipe data.
- Re-check authorization inside the handler against the authenticated session. The model selecting a tool is not authorization; the handler must verify the caller may perform this specific action on this specific resource.
- Make handlers safe to retry: design mutations to be idempotent (or guarded by an idempotency key) so a re-issued call after a timeout does not double-act.
- Scope each tool narrowly. A surgical tool the model uses correctly beats a powerful do-everything tool it misuses.

---

### [RULE] Choose transport by deployment and authenticate remote servers  (path: /src/mcp)
<!-- scope: folder | priority: high | advisory -->

Transport is a deployment decision, and the moment a server is reachable over the network it needs authentication.

- Use the stdio transport for a local server launched by one client (a desktop AI app, an editor): no network surface, the client owns the process lifecycle.
- Use streamable HTTP for a remote or multi-client server. It is the current HTTP transport; do not build new servers on the deprecated HTTP+SSE transport.
- Authenticate every HTTP server. At minimum require a bearer token; for third-party or user-facing servers, implement the MCP OAuth flow so each user authorizes with their own identity. An unauthenticated remote MCP server is an open door to whatever its tools can do.
- Validate the `Origin` header and bind local HTTP servers to `127.0.0.1` to prevent DNS-rebinding and cross-site access. Keep secrets in server env, never in tool output or logs.
- Pass the authenticated identity into tool handlers so per-tool authorization checks have a subject to check against.

---

### [MEMORY] Choose the right primitive: tool vs resource vs prompt  (path: /src/mcp)

MCP exposes three primitives with different control models, and picking the wrong one is a recurring design mistake.

- Tools are model-controlled: the LLM decides to call them to take an action or fetch dynamic data. Use a tool when the model should act (mutations, searches, computations, live lookups).
- Resources are application-controlled: readable, addressable context (a file, a record, a schema) the host app or user attaches. Use a resource for content the model should be able to read but not "invoke" - it has no side effect and is referenced by URI.
- Prompts are user-controlled: parameterized templates a user explicitly selects (slash commands, canned workflows). Use a prompt to package a reusable interaction the user triggers, not the model.
- A read-only lookup can legitimately be either a resource (attachable context) or a tool (model fetches on demand); choose by whether the host or the model should decide when it enters context. When unsure for actions, a narrowly-scoped tool is the safe default.

See /src/mcp for the tool-contract and destructive-action rules.

---

### [SKILL] mcp-tool-authoring-checklist  (path: /)

---
name: mcp-tool-authoring-checklist
description: Checklist for adding or changing an MCP tool, resource, or prompt. Run before merging any change to a Model Context Protocol server.
---

# MCP tool authoring checklist

## Contract (model-facing)
- [ ] Tool name is action+object and matches what the model would search for.
- [ ] Description states what it does, when to use it, when NOT to, and what it returns - written for the model, with units/formats spelled out.
- [ ] Input schema declared (Zod/JSON Schema) with `.describe()` on non-obvious fields; handler reads only validated input.
- [ ] Result is structured and model-readable; failures use the error channel (`isError`), not opaque throws.

## Safety
- [ ] Effects match the description exactly; mutating tools say so.
- [ ] Destructive/irreversible actions require `confirm: true` or support `dryRun`.
- [ ] Handler re-checks authorization against the authenticated session for this resource.
- [ ] Mutations are idempotent or guarded by an idempotency key (safe to retry).
- [ ] Tool is narrowly scoped, not a do-everything tool.

## Transport & auth
- [ ] stdio for local single-client; streamable HTTP for remote (not the deprecated SSE transport).
- [ ] Every HTTP server authenticates (bearer or OAuth); local HTTP binds to 127.0.0.1 and validates `Origin`.
- [ ] No secrets in tool output or logs; authenticated identity flows into handlers.

## Primitive choice
- [ ] Capability mapped to the right primitive: tool (model acts), resource (readable context), prompt (user-triggered template).
