---
name: mcp-server-tools
description: >
  Adding, removing or changing a tool on RedAmon's INBOUND MCP server, where external
  agents connect in with a personal access token. The wiki API reference is generated
  from the server's live tools/list and goes stale silently, and each tool's annotations
  and declared scopes are published to connected clients.
  Trigger: editing a registerTool call in webapp/src/lib/mcp/server.ts; editing tools.ts,
  writeTools.ts, scopeCopy.ts or apiReference.ts in webapp/src/lib/mcp/; changing the
  scope list, default scopes, expiry presets or token prefix in webapp/src/lib/mcpAuth.ts.
license: MIT
metadata:
  author: redamon
  version: "1.0.0"
  scope: [webapp]
  auto_invoke:
    - "Adding, removing or changing a tool on the inbound MCP server"
    - "Changing MCP token scopes, their UI wording, or the MCP API reference generator"
---

## When to Use

- Adding, removing or renaming an inbound MCP tool, or changing its description,
  arguments, annotations or scopes.
- Changing a token scope, its label or blurb, the expiry presets or the token prefix.

For a tool RedAmon's OWN agent calls (outbound, `mcp/servers/`), use
`agentic-tool-integration`. For making a recon setting writable over MCP, use
`project-settings-cascade`. The tools that exist are listed by the server itself and by
the generated reference page, never by this skill.

---

## Critical Rules

- **ALWAYS run `npm run docs:mcp` in `webapp/` after any change this skill's trigger
  names, and commit `MCP-API-Reference.md` inside the `redamon.wiki` repo.** A main-repo
  commit moves only the submodule pointer. The page is rendered by
  [apiReference.ts](../../webapp/src/lib/mcp/apiReference.ts); a hand edit is overwritten
  by the next run.
- **NEVER set `destructiveHint: false` on a tool that overwrites or aborts existing
  state.** In the MCP spec `false` means only additive updates, and clients use it to
  decide when to ask the user first. No test checks this.
- **ALWAYS add an `EXAMPLE_EXTRA_ARGS` entry in
  [apiReference.ts](../../webapp/src/lib/mcp/apiReference.ts) when a tool's body requires
  an argument its JSON Schema marks optional.** The generated example sends only
  schema-required arguments, and the test calls every example.
- **ALWAYS give a new tool an `ONBOARDING_PLAYBOOK` entry and a capability area in
  [playbook.ts](../../webapp/src/lib/mcp/playbook.ts).** The Agent Onboarding pack is
  generated from the live `tools/list`, and a coverage test in
  [onboarding.test.ts](../../webapp/src/lib/mcp/onboarding.test.ts) fails the moment a
  tool ships without `whenToUse` + gotchas, or sits in zero or two capability areas. Also
  decide whether any profile in [profiles.ts](../../webapp/src/lib/mcp/profiles.ts) should
  `leansOn` it; that half is editorial and is not enforced.
- **A tool that reads NO backend must be added to `BACKEND_FREE_TOOLS` in
  [apiReference.test.ts](../../webapp/src/lib/mcp/apiReference.test.ts).** That file proves a
  tool's declared scopes are enough by calling it and expecting the generic database failure,
  because every Prisma model is mocked away. A tool derived purely from constants
  (`describe_recon_settings`, `list_recon_presets`) SUCCEEDS instead, and would otherwise read
  as a failure. Do not relax the assertion; name the tool.
- **When a capability flips from "cannot" to "can", sweep the old claims.** Add the old
  claim's phrasing to `STALE_CLAIMS` in
  [toolNameDrift.test.ts](../../webapp/src/lib/mcp/toolNameDrift.test.ts), and make sure
  every file carrying agent-facing strings is in its `AGENT_FACING` list: the "nothing here
  can mute" lines outlived the feature in `server.ts` and `profiles.ts`, which were outside it.
- **Keep agent op names out of agent-facing files.** `TOOL_SHAPED` only knows the verbs it
  lists, so a new tool's verb goes there; an op literal such as `list_muted` or `mute_many`
  in an agent-facing file then reads as an unknown tool. Wrap ops in
  [triageGraph.ts](../../webapp/src/lib/mcp/triageGraph.ts) helpers instead.
- **Watch the `write` bucket headroom.** [apiReference.test.ts](../../webapp/src/lib/mcp/apiReference.test.ts)
  calls every tool with ONE token inside one test, against the `write` limit (10/min by
  default). A new write tool that overflows it must raise that test's limit, not drop a call.
- **A required ARRAY argument needs an `EXAMPLE_EXTRA_ARGS` entry.** `placeholderFor` has no
  array branch, so the generated example would name no item (`mute_findings`' `findingIds`).
- **A new scope gates writes; reads go under an existing read scope.** Every `ScopeAccess` in
  [scopeCopy.ts](../../webapp/src/lib/mcp/scopeCopy.ts) is `read` or `write` except `kali:exec`,
  the one `read-write` scope. A tool that only reads what a write needs (`get_finding_evidence`
  for `submit_finding_review`) belongs under the read scope, so a read-only token can see
  what an agent would act on.
- **Order the body's checks: `requireScope` first, then no `McpToolError` before the first
  backend read.** The apiReference test proves declared scopes by calling each example and
  expecting the mocked database failure; a refusal on the example's placeholder arguments
  ahead of that read looks like a scope gap. A tool that STARTS something checks access (and
  its own caps) before it spends a rate token, so a refused start costs the caller nothing.
- **Free text a caller writes goes in `UNAUDITED_ARGS`** in
  [server.ts](../../webapp/src/lib/mcp/server.ts). `auditDetail` copies every other string
  argument into the audit row, which also prints as a console line; a quote is target text,
  and a reason is recorded, better, by the tool that owns it.

---

## Pattern: registering a tool

Copy the shape of an existing `registerTool` call in
[server.ts](../../webapp/src/lib/mcp/server.ts): `annotations`,
`_meta: scopesMeta({ required, conditional })` naming exactly the scopes the tool body
enforces with `requireScope`, and `inputSchema`. A scope that applies only to a
particular argument goes under `conditional`, and the arguments that trigger it need a
`CONDITIONAL_TRIGGERS` entry in
[apiReference.test.ts](../../webapp/src/lib/mcp/apiReference.test.ts).

---

## Commands

```bash
cd webapp
npm run docs:mcp                 # rewrites ../redamon.wiki/MCP-API-Reference.md
npx vitest run src/lib/mcp/      # scope, example-call, onboarding-coverage and stale-page checks

cd ../redamon.wiki
git add MCP-API-Reference.md
git commit -m "docs: regenerate MCP API reference"
```

---

## Resources

- [MCP-Server.md](../../redamon.wiki/MCP-Server.md), section "Regenerating the API reference" - what the page is built from, and the stale-page test
- [README.MCP.SERVER.md](../../docs/readmes/README.MCP.SERVER.md) - security model, settings allowlist, deploy wiring; §3.1-3.2 cover Agent Profiles (never an authorization input) and how the onboarding pack is generated
- Related skills: `agentic-tool-integration`, `project-settings-cascade`, `redamon-testing`
