{"_id":"@apitella/scan","_rev":"2-a40449ad0b997db83bbfe2c0226a5b6b","name":"@apitella/scan","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@apitella/scan","version":"0.1.0","keywords":["mcp","model-context-protocol","security","linter","ci","agents","llm"],"license":"MIT","_id":"@apitella/scan@0.1.0","maintainers":[{"name":"dodogeny","email":"dil.neemuth@gmail.com"}],"homepage":"https://apitella.com/scan","bugs":{"url":"https://github.com/dodogeny/apitella-saas/issues"},"bin":{"apitella-scan":"dist/index.js"},"dist":{"shasum":"de9c540b444c3bf29f23b3c8dad93b0bd5c9cfc9","tarball":"https://registry.npmjs.org/@apitella/scan/-/scan-0.1.0.tgz","fileCount":8,"integrity":"sha512-L97gVpElUgfmgqRhZOoHpXvIrZ0vACQ1SCTU8nilfc+wF3HXyuo2FySF2/GWgLVas1NkkZ42o2u/YGfO7CaTqw==","signatures":[{"sig":"MEUCIQD6Ty2QaRjGGF9dNXplvg50MbHs42Pr2ckyU6i+XpPKsgIgJKge3m8x/T0NL9GrxVlXiIFkwO+k2d6GqMhvXJpTAxs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":34045},"type":"module","engines":{"node":">=20"},"gitHead":"8064137dee2775ac9ddc85e870be8fb1e8fdefa8","scripts":{"dev":"tsx src/index.ts","test":"node --test --import tsx test/analyze.test.ts test/baseline.test.ts","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build"},"_npmUser":{"name":"dodogeny","email":"dil.neemuth@gmail.com"},"repository":{"url":"git+https://github.com/dodogeny/apitella-saas.git","type":"git","directory":"cli"},"_npmVersion":"11.16.0","description":"Check an MCP server's tool surface for safety-annotation gaps and prompt-injection risks — at build time, in CI, or against a live URL.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"@modelcontextprotocol/sdk":"^1.11.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.2","typescript":"^5.7.2","@types/node":"^22.10.2"},"_npmOperationalInternal":{"tmp":"tmp/scan_0.1.0_1788634555034_0.4688685301274187","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@apitella/scan","version":"0.2.0","description":"Check an MCP server's tool surface for safety-annotation gaps and prompt-injection risks — at build time, in CI, or against a live URL.","type":"module","bin":{"apitella-scan":"dist/index.js"},"engines":{"node":">=20"},"scripts":{"build":"tsc -p tsconfig.json","dev":"tsx src/index.ts","test":"node --test --import tsx test/analyze.test.ts test/baseline.test.ts test/output.test.ts","prepublishOnly":"npm run build"},"keywords":["mcp","model-context-protocol","security","linter","ci","agents","llm"],"license":"MIT","homepage":"https://apitella.com/scan","repository":{"type":"git","url":"git+https://github.com/dodogeny/apitella-saas.git","directory":"cli"},"bugs":{"url":"https://github.com/dodogeny/apitella-saas/issues"},"publishConfig":{"access":"public"},"dependencies":{"@modelcontextprotocol/sdk":"^1.11.0"},"devDependencies":{"@types/node":"^22.10.2","tsx":"^4.19.2","typescript":"^5.7.2"},"gitHead":"3cbb75e5116975193a278f28302e679849139785","_id":"@apitella/scan@0.2.0","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-ogKyZEWbdo7GdBRSnFh8YbcHlTEmpAP5zK1pZ/iMtZxlvJdXDxK+2TIodfYFaG0Dcn3PZ7tff0BaSY14O/p4Tg==","shasum":"7346e5846ce7ab0bdf1665c6e3453f2df605a13f","tarball":"https://registry.npmjs.org/@apitella/scan/-/scan-0.2.0.tgz","fileCount":9,"unpackedSize":44322,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCpRQ8bPbPmyn/lrLDsiNr0BtD8Pf5MUUXUevb4LGMv5wIgGQW7ea0B1TxL1RYHvzH/+MF1DmTHVBocaUnUfMdj0YQ="}]},"_npmUser":{"name":"dodogeny","email":"dil.neemuth@gmail.com"},"directories":{},"maintainers":[{"name":"dodogeny","email":"dil.neemuth@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/scan_0.2.0_1788686227007_0.12053352975758402"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-05T18:55:54.799Z","modified":"2026-09-06T09:17:07.330Z","0.1.0":"2026-09-05T18:55:55.202Z","0.2.0":"2026-09-06T09:17:07.136Z"},"bugs":{"url":"https://github.com/dodogeny/apitella-saas/issues"},"license":"MIT","homepage":"https://apitella.com/scan","keywords":["mcp","model-context-protocol","security","linter","ci","agents","llm"],"repository":{"type":"git","url":"git+https://github.com/dodogeny/apitella-saas.git","directory":"cli"},"description":"Check an MCP server's tool surface for safety-annotation gaps and prompt-injection risks — at build time, in CI, or against a live URL.","maintainers":[{"name":"dodogeny","email":"dil.neemuth@gmail.com"}],"readme":"# @apitella/scan\n\nCheck an MCP server's tool surface for the gaps that bite agents in production —\nmissing safety annotations, descriptions that contradict their own hints, credential\nstrings and prompt-injection phrasing in tool text — at **build time**, in **CI**, or\nagainst a **live URL**.\n\nSame checks as the free scanner at [apitella.com/scan](https://apitella.com/scan),\nrunnable where you build the server.\n\n```\nnpx @apitella/scan http://localhost:3000/mcp\n```\n\n```\nApitella scan  my-warehouse-mcp v1.4.0\n6 tools · 2 resources\n\n  ▲ create_shipment  [no-safety-hints]\n    Neither readOnlyHint nor destructiveHint is declared — an agent can't tell\n    whether this is safe to auto-approve.\n  ▲ delete_record  [destructive-hint-mismatch]\n    Description reads as destructive, but destructiveHint isn't set to true — an\n    agent skipping confirmation has no signal this is dangerous.\n  · server  [no-version-signal]\n    No changelog or version signal on this server — if any of this changes,\n    nothing tells you unless it's being monitored.\n\n  2 warnings — see above.\n```\n\n## Usage\n\n|                                         |                                                                                     |\n| --------------------------------------- | ----------------------------------------------------------------------------------- |\n| `npx @apitella/scan <url>`              | Connect (Streamable HTTP, falling back to SSE) and scan. Works against `localhost`. |\n| `npx @apitella/scan --input tools.json` | Scan a saved `tools/list` dump — no running server needed.                          |\n| `-H, --header \"X-API-Key: ...\"`         | Extra request header. Repeatable.                                                   |\n| `--token <t>`                           | Bearer token shorthand.                                                             |\n| `--baseline [file]`                     | Diff the scan against a committed snapshot (default `.apitella/baseline.json`).     |\n| `--update-baseline`                     | Write the current surface to the baseline file and exit 0.                          |\n| `--format <fmt>`                        | `text` (default), `json`, `sarif` (code scanning), `markdown` (PR comment).         |\n| `--json`                                | Deprecated alias for `--format json`.                                               |\n| `--fail-on <error\\|warning>`            | Non-zero exit threshold (default: `warning`). Ignored with `--baseline`.            |\n| `--no-fail`                             | Always exit 0 unless the scan itself failed.                                        |\n\nAll formats keep the same exit codes, so `--format sarif` / `markdown` still gate a build.\n\n### Exit codes\n\n| Code | Meaning                                                                  |\n| ---- | ------------------------------------------------------------------------ |\n| `0`  | Clean, below `--fail-on`, no regression vs. `--baseline`, or `--no-fail` |\n| `1`  | Findings at or above `--fail-on`, or a regression vs. `--baseline`       |\n| `2`  | Usage error, or couldn't reach / parse the target                        |\n\n## Baseline: make schema changes show up in code review\n\nCommit a snapshot of your server's surface, then fail a PR only when the change makes\nit **worse** — a tool removed, a tool that lost its `readOnlyHint` / `destructiveHint`,\na resource whose `mimeType` changed, or a new error/warning. Pre-existing findings\nyou've decided to live with don't fail the build.\n\n```\n# once, checked in:\nnpx @apitella/scan http://localhost:3000/mcp --update-baseline\ngit add .apitella/baseline.json\n\n# on every PR:\nnpx @apitella/scan http://localhost:3000/mcp --baseline\n```\n\n```\nvs. baseline  .apitella/baseline.json\n  − removed  get_user\n  + added    delete_user\n  ! delete_user lost its safety hint — it declared destructiveHint in the baseline and now declares neither.\n  ▲ new      delete_user  [no-safety-hints]\n\n  This change makes the MCP surface worse than the baseline.\n```\n\nWhen a change is intentional, re-run `--update-baseline` and commit the new snapshot —\nthe diff lands in the PR alongside the code.\n\n## In CI\n\n### GitHub Action\n\n```yaml\n# .github/workflows/mcp-scan.yml\n- uses: actions/checkout@v4\n- uses: actions/setup-node@v4\n  with: { node-version: 20 }\n- run: |\n    npm ci && npm run build\n    node dist/server.js &\n    npx --yes wait-on http://localhost:3000/mcp\n- uses: dodogeny/apitella-scan-action@v1\n  with:\n    url: http://localhost:3000/mcp\n    baseline: .apitella/baseline.json\n```\n\nOn `pull_request` events the action posts a **sticky PR comment** with the result and\nbaseline diff (needs `permissions: pull-requests: write`; `comment: false` to disable).\nOther inputs: `url`/`input`, `baseline`, `fail-on`, `headers`, `format`, `output-file`,\n`package-version`. Full example: [`examples/mcp-scan.yml`](./examples/mcp-scan.yml).\n\n### SARIF → GitHub code scanning\n\n```yaml\n- uses: dodogeny/apitella-scan-action@v1\n  with:\n    url: http://localhost:3000/mcp\n    format: sarif\n    output-file: apitella.sarif\n- uses: github/codeql-action/upload-sarif@v3\n  with:\n    sarif_file: apitella.sarif\n```\n\n### Plain npx\n\n```yaml\n- run: npx @apitella/scan --input tools.json --fail-on error\n```\n\n## What it checks\n\n- **`no-safety-hints`** — a tool declares neither `readOnlyHint` nor `destructiveHint`.\n- **`destructive-hint-mismatch`** — the description reads as destructive (\"deletes…\",\n  \"wipes…\") but `destructiveHint` isn't `true`.\n- **`readonly-hint-contradicted`** — `readOnlyHint: true` on a tool that takes a\n  `content` / `body` / `payload` / `data` parameter.\n- **`openworld-hint-contradicted`** — `openWorldHint: false` on a tool whose description\n  says it reaches arbitrary external URLs or the open web.\n- **`instruction-injection`** — tool or parameter text contains instruction-override\n  phrasing or invisible characters aimed at the model reading it.\n- **`credential-exposed`** — an AWS key, GitHub/Slack token, or private-key block in\n  tool text.\n- **`insecure-transport`** — the server URL is plain `http://`.\n\nThe CVE lookup and the LLM-backed semantic injection scan that\n[apitella.com](https://apitella.com) runs are not in this CLI — it stays offline and\nfast. Continuous monitoring of a deployed server (and of servers you depend on) is the\nhosted product.\n\n## What it does not do\n\nIt does not send your schema anywhere. Everything runs locally.\n\n## Development\n\n```\nnpm install\nnpm run build\nnpm test\nnpm run dev -- http://localhost:3000/mcp   # run from source\n```\n\nThe rule logic in `src/analyze.ts` is a port of the transport-free checks in the\nApitella API (`api/src/lib/scanAnalysis.ts` and the synchronous half of\n`api/src/lib/securityChecks.ts`). The two are kept in sync by hand for now; the tests\nin `test/` lock the CLI's behaviour so a divergence is caught.\n","readmeFilename":"README.md"}