{"_id":"@carlosfevernova/mcp-armor","name":"@carlosfevernova/mcp-armor","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@carlosfevernova/mcp-armor","version":"0.1.0","description":"Drop-in proxy for MCP clients — lazy-loads tool schemas (60-95% token savings), scans for SSRF and command injection, wraps arguments in Zod types. Zero runtime deps beyond zod.","keywords":["mcp","model-context-protocol","claude","anthropic","openai","token-optimization","security","ssrf","zod","proxy"],"author":{"name":"Carlos F. Vernova"},"license":"MIT","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"engines":{"node":">=20"},"sideEffects":false,"scripts":{"build":"tsup","test":"vitest run","typecheck":"tsc --noEmit","prepublishOnly":"npm run build && npm test"},"dependencies":{"zod":"^3.23.8"},"devDependencies":{"tsup":"8.3.5","typescript":"5.6.3","vitest":"2.1.5"},"repository":{"type":"git","url":"git+https://github.com/carlosfevernova/mcp-armor.git"},"bugs":{"url":"https://github.com/carlosfevernova/mcp-armor/issues"},"homepage":"https://github.com/carlosfevernova/mcp-armor","allowScripts":{"esbuild@0.24.2":true,"esbuild@0.21.5":true},"gitHead":"2f48b4e7cf327bb6dbebb14beb59e26239627453","_id":"@carlosfevernova/mcp-armor@0.1.0","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-T3of+aTD8GNMVJ50uMLqm3QuxOM06+Uj3NiPfsiOZ2bZCgbDnWq7t+uEs2mjMp6+pEC4Q/IUFzlM+kyouvhvRA==","shasum":"33af5e81c67178b7f6712b9a4bee044cf98b041d","tarball":"https://registry.npmjs.org/@carlosfevernova/mcp-armor/-/mcp-armor-0.1.0.tgz","fileCount":9,"unpackedSize":123081,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICBia6e3SEKWo1CoGythYkPCt2FwHq7VZU+A0fVp/hTbAiEA8mgTl/ztO78z0ZIbXoM5JbuO5WcMX3dPpCXTq5bdoY4="}]},"_npmUser":{"name":"carlosfevernova","email":"carlosfevernova@hotmail.com"},"directories":{},"maintainers":[{"name":"carlosfevernova","email":"carlosfevernova@hotmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mcp-armor_0.1.0_1787841375445_0.08246463195683873"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-27T14:36:15.314Z","0.1.0":"2026-08-27T14:36:15.583Z","modified":"2026-08-27T14:36:15.799Z"},"maintainers":[{"name":"carlosfevernova","email":"carlosfevernova@hotmail.com"}],"description":"Drop-in proxy for MCP clients — lazy-loads tool schemas (60-95% token savings), scans for SSRF and command injection, wraps arguments in Zod types. Zero runtime deps beyond zod.","homepage":"https://github.com/carlosfevernova/mcp-armor","keywords":["mcp","model-context-protocol","claude","anthropic","openai","token-optimization","security","ssrf","zod","proxy"],"repository":{"type":"git","url":"git+https://github.com/carlosfevernova/mcp-armor.git"},"author":{"name":"Carlos F. Vernova"},"bugs":{"url":"https://github.com/carlosfevernova/mcp-armor/issues"},"license":"MIT","readme":"# mcp-armor\n\n**Your MCP client is burning 300 KB of tokens per session. This fixes it in 6 lines.**\n\nDrop-in proxy for MCP (Model Context Protocol) clients. Three things it does that nobody else does in one package:\n\n1. **Lazy-loads tool schemas** — sends a minimal name+one-liner list to the LLM every turn, and only expands the full JSON Schema when the model actually picks a tool. **60–95% token reduction** on typical sessions.\n2. **Scans tool defs and runtime arguments for injection attacks** — SSRF (metadata service + private IPs + localhost), command injection (shell metacharacters, `child_process` hints), path traversal, SQL tautology, prompt-injection markers. Blocks high-severity by default.\n3. **Zod-first tool definitions** — write your tool once with a Zod schema, get a type-safe handler + auto-generated JSON Schema. No hand-writing schemas twice.\n\n```bash\nnpm i @carlosfevernova/mcp-armor\n```\n\n*(The unscoped `mcp-armor` name was taken by an unrelated package; this one lives in the `@carlosfevernova` scope.)*\n\n## Why this exists\n\nAn [arxiv paper from Nov 2026](https://arxiv.org/html/2511.20920v1) audited the MCP server registry and found **36.7% of servers had SSRF-friendly designs** and **43% had unsafe command execution**. Meanwhile, [Builder Radar](https://buttondown.com/Builder-Radar/archive/builder-radar-week-of-august-16-2026/) tracked the \"MCP tool overhead\" problem: 15,000 tokens of tool defs per turn × 20 turns = **300,000 tokens burned before your model does any actual work**.\n\n`mcp-armor` sits between your MCP client and your tools and solves both.\n\n## 30-second example\n\n```ts\nimport { MCPProxy, defineTool } from \"mcp-armor\";\nimport { z } from \"zod\";\n\nconst searchTool = defineTool({\n  name: \"search\",\n  description: \"Full-text search over the corpus.\",\n  input: z.object({\n    query: z.string().min(1),\n    limit: z.number().int().max(100).default(10),\n  }),\n  handler: async ({ query, limit }) => {\n    const rows = await db.query(query, limit);\n    return { rows };\n  },\n});\n\nconst proxy = new MCPProxy(\n  [searchTool],\n  { search: searchTool.handler },\n  { onHighSeverity: \"block\" }\n);\n\n// Wire into your MCP server's list_tools handler:\nserver.setRequestHandler(ListToolsRequestSchema, () => proxy.listTools());\nserver.setRequestHandler(CallToolRequestSchema, async (req) => {\n  const r = await proxy.callTool(req.params.name, req.params.arguments);\n  if (!r.ok) throw new Error(r.blockReason);\n  return { content: [{ type: \"text\", text: JSON.stringify(r.data) }] };\n});\n```\n\n## What you get\n\n### Token compression (`lazyLoadTools`)\n\nGiven 20 tools with ~800-char schemas each, the compression stats look like this:\n\n```ts\nconst stats = proxy.metrics();\n// { totalTools: 20,\n//   fullSchemaTokenEstimate: 4200,\n//   minimalTokenEstimate: 260,\n//   compressionSavingsPercent: 94 }\n```\n\nThe trick: MCP tool schemas are fat (`inputSchema` is verbose JSON Schema), but every model turn only needs to know **which tools exist and roughly what each does**. Full schemas are only sent when the model actually picks a tool via `getFullSchema(name)`.\n\n### Security scan (`scanTool`, `scanToolInput`)\n\nStatic scan runs at proxy construction and flags:\n\n- SSRF gadgets (cloud metadata endpoints, private-IP references in descriptions)\n- Command-injection hints (`child_process`, `exec`, `os.system` mentions)\n- Suspicious property names (`command`, `shell`, `sql`, `query` accepting free-form strings)\n\nRuntime scan runs on every `callTool` and catches:\n\n- SSRF attempts (metadata IPs, RFC 1918 private ranges, `localhost`, `0.0.0.0`)\n- Unsafe URL schemes (`file://`, `ftp://`, `data://`, `gopher://`)\n- Shell metacharacters (`;`, `&`, `|`, backticks, `$(`, `&&`, `||`)\n- Path traversal sequences (`../`)\n- Classic SQL injection tautologies (`' OR '1'='1`)\n- Prompt-injection markers (\"ignore previous instructions\" et al.)\n\nYou choose the reaction with `onHighSeverity: \"block\" | \"warn\" | \"ignore\"`.\n\n### Type-safe handlers (`defineTool`, `validateInput`)\n\n```ts\nconst tool = defineTool({\n  name: \"book_flight\",\n  input: z.object({\n    from: z.string().length(3),\n    to: z.string().length(3),\n    date: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/),\n  }),\n  handler: async ({ from, to, date }) => {\n    // TypeScript already knows the shape here — no manual type assertion.\n    return { pnr: await amadeus.book(from, to, date) };\n  },\n});\n```\n\nThe Zod schema is auto-converted to JSON Schema for MCP compatibility. On the boundary, `validateInput` runs the raw argument object through Zod's `safeParse` and returns a discriminated union — no try/catch dance.\n\n## API\n\n- `lazyLoadTools(tools)` → `LazyToolset` with `.minimal`, `.getFullSchema()`, `.stats()`\n- `scanTool(tool)` → static security scan of a tool definition\n- `scanToolInput(tool, args)` → runtime security scan of arguments\n- `defineTool({ name, input, handler })` → typed tool with auto-generated inputSchema\n- `validateInput(tool, args)` → `{ ok: true, data } | { ok: false, issues }`\n- `zodToJsonSchema(schema)` → standalone converter (covers 80% of MCP shapes)\n- `class MCPProxy(tools, handlers, opts)` → the drop-in wrapper\n\n## Design choices\n\n- **Zero runtime deps besides `zod`.** `zod` is already the standard for MCP tool input validation; adding it doesn't cost you anything.\n- **Node 20+.** Uses native `AbortController`, no polyfills.\n- **Works with any MCP transport.** stdio, SSE, or WebSocket — the proxy sits above transport concerns.\n- **Sane defaults.** Blocks high-severity findings by default. Change with `onHighSeverity: \"warn\"` if you'd rather log and continue.\n\n## Related\n\n- [`vercel-armor`](https://www.npmjs.com/package/vercel-armor) — 4-layer armor for Vercel serverless APIs. Same author, same DNA, different layer.\n\n## License\n\nMIT — Carlos F. Vernova\n","readmeFilename":"README.md","_rev":"1-9e9d360dae7ee19dc5c1e717711a9401"}