{"_id":"@calvinmclean/reflect-mcp","_rev":"5-5935c1321cc830ab5a6126e8062fc730","name":"@calvinmclean/reflect-mcp","dist-tags":{"latest":"0.0.6"},"versions":{"0.0.2":{"name":"@calvinmclean/reflect-mcp","version":"0.0.2","author":{"name":"calvinmclean"},"license":"MIT","_id":"@calvinmclean/reflect-mcp@0.0.2","maintainers":[{"name":"calvinmclean","email":"calvinlmc@gmail.com"}],"homepage":"https://github.com/calvinmclean/mcp-zoo/tree/main/reflect-mcp#readme","bugs":{"url":"https://github.com/calvinmclean/mcp-zoo/issues"},"bin":{"reflect-mcp":"dist/index.js"},"dist":{"shasum":"8c0a52c9fc961d839f69e9714fdaa555fae31f84","tarball":"https://registry.npmjs.org/@calvinmclean/reflect-mcp/-/reflect-mcp-0.0.2.tgz","fileCount":4,"integrity":"sha512-DxV6LQ/NIdi18EIPQP54gmxWEma6zuCu6fn5ozBFrwkIcqmOX8bbxXoyPrD4ThGEXnDSN3B3FbAqBPGMHD+7iw==","signatures":[{"sig":"MEQCIHMkLeua45IKM9+RHc1ixGSxGwcIC5B9p8zoXmoOs6IWAiBoZsLceMy4dJ2Z7qOc1GIS4gRv/zoVcNjpcD/eXkn/Zw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":37494},"main":"dist/index.js","pnpm":{"ignoredBuiltDependencies":["esbuild"]},"type":"module","engines":{"node":">=24"},"gitHead":"ac3e18437a1d1d6e4587eef3d98fd27f6a303cec","scripts":{"dev":"tsx src/index.ts","lint":"biome check src","build":"tsc","clean":"rm -rf dist node_modules","tunnel":"cloudflared tunnel run --url http://localhost:8099 dev","lint:fix":"biome check --write src","typecheck":"tsc --noEmit","dev:tunnel":"concurrently -kn dev,tunnel -c blue,magenta \"pnpm dev\" \"pnpm tunnel\"","prepublishOnly":"pnpm build"},"_npmUser":{"name":"calvinmclean","email":"calvinlmc@gmail.com"},"repository":{"url":"git+https://github.com/calvinmclean/mcp-zoo.git","type":"git","directory":"reflect-mcp"},"_npmVersion":"11.11.0","description":"Debug MCP server that echoes its launch command/args, env, and inbound HTTP headers via a single 'echo' tool.","directories":{},"_nodeVersion":"25.8.1","dependencies":{"zod":"^4.3.6","express":"^5.2.1","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.33.2","devDependencies":{"tsx":"^4.21.0","typescript":"^6.0.3","@types/node":"^25.6.0","concurrently":"^9.2.1","@biomejs/biome":"^2.4.13","@types/express":"^5.0.6"},"_npmOperationalInternal":{"tmp":"tmp/reflect-mcp_0.0.2_1777659182779_0.3646642130876352","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"@calvinmclean/reflect-mcp","version":"0.0.3","author":{"name":"calvinmclean"},"license":"MIT","_id":"@calvinmclean/reflect-mcp@0.0.3","maintainers":[{"name":"calvinmclean","email":"calvinlmc@gmail.com"}],"homepage":"https://github.com/calvinmclean/mcp-zoo/tree/main/reflect-mcp#readme","bugs":{"url":"https://github.com/calvinmclean/mcp-zoo/issues"},"bin":{"reflect-mcp":"dist/index.js"},"dist":{"shasum":"8fb4a19d21830d9a5d332c0730a6826bb6f7ddd8","tarball":"https://registry.npmjs.org/@calvinmclean/reflect-mcp/-/reflect-mcp-0.0.3.tgz","fileCount":4,"integrity":"sha512-GFFGoVqeMpC3/a6N7gw/RIEEmQQBeO6JZe00ty3znJmVy++rdsEBm9kbpcqfp+ti4HAiQEo7N1iq51T1xqfbTg==","signatures":[{"sig":"MEUCIEbE3k00KGDaL4yj/SQHrTwiE0ZfDTiy/HdEhu51+DwSAiEAzoaOD/jwwnWMeDMnB4MO7aa4InSke6thjLYwvZtAnj4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@calvinmclean%2freflect-mcp@0.0.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":37494},"main":"dist/index.js","pnpm":{"ignoredBuiltDependencies":["esbuild"]},"type":"module","engines":{"node":">=24"},"gitHead":"31e9c31fd3526283a882e3d8b35dcfea5d5a9d2f","scripts":{"dev":"tsx src/index.ts","lint":"biome check src","build":"tsc","clean":"rm -rf dist node_modules","tunnel":"cloudflared tunnel run --url http://localhost:8099 dev","lint:fix":"biome check --write src","typecheck":"tsc --noEmit","dev:tunnel":"concurrently -kn dev,tunnel -c blue,magenta \"pnpm dev\" \"pnpm tunnel\"","prepublishOnly":"pnpm build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a9d8f530-7d0f-4eeb-8ae5-dfa086240a71"}},"repository":{"url":"git+https://github.com/calvinmclean/mcp-zoo.git","type":"git","directory":"reflect-mcp"},"_npmVersion":"11.13.0","description":"Debug MCP server that echoes its launch command/args, env, and inbound HTTP headers via a single 'echo' tool.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"zod":"^4.3.6","express":"^5.2.1","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.33.2","devDependencies":{"tsx":"^4.21.0","typescript":"^6.0.3","@types/node":"^25.6.0","concurrently":"^9.2.1","@biomejs/biome":"^2.4.13","@types/express":"^5.0.6"},"_npmOperationalInternal":{"tmp":"tmp/reflect-mcp_0.0.3_1777659372046_0.799471955699534","host":"s3://npm-registry-packages-npm-production"}},"0.0.4":{"name":"@calvinmclean/reflect-mcp","version":"0.0.4","author":{"name":"calvinmclean"},"license":"MIT","_id":"@calvinmclean/reflect-mcp@0.0.4","maintainers":[{"name":"calvinmclean","email":"calvinlmc@gmail.com"}],"homepage":"https://github.com/calvinmclean/mcp-zoo/tree/main/reflect-mcp#readme","bugs":{"url":"https://github.com/calvinmclean/mcp-zoo/issues"},"bin":{"reflect-mcp":"dist/index.js"},"dist":{"shasum":"c3566dc6afc1289956931911dd79e9be85e46721","tarball":"https://registry.npmjs.org/@calvinmclean/reflect-mcp/-/reflect-mcp-0.0.4.tgz","fileCount":4,"integrity":"sha512-EgBVk3ILR2EkhQd6SFN0WpZKH9Kxosst5QLd9R4TRjzcxFA/zz1fwQJR0EF2e1p2q0wiahXk11mR+ifwzFkJNA==","signatures":[{"sig":"MEUCICszmg8FeNYGQneucZxaR6DH2JKieIbnAObx8B7gwzniAiEAoShnmC/NupL4QMdCKGRnj56DY+lsAWWBmqOuAYeP7/U=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@calvinmclean%2freflect-mcp@0.0.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":38558},"main":"dist/index.js","pnpm":{"ignoredBuiltDependencies":["esbuild"]},"type":"module","engines":{"node":">=24"},"gitHead":"7e12a80093ab6da07f710afc5861acf0c1ce553d","scripts":{"dev":"tsx src/index.ts","lint":"biome check src","build":"tsc","clean":"rm -rf dist node_modules","tunnel":"cloudflared tunnel run --url http://localhost:8099 dev","lint:fix":"biome check --write src","typecheck":"tsc --noEmit","dev:tunnel":"concurrently -kn dev,tunnel -c blue,magenta \"pnpm dev\" \"pnpm tunnel\"","prepublishOnly":"pnpm build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a9d8f530-7d0f-4eeb-8ae5-dfa086240a71"}},"repository":{"url":"git+https://github.com/calvinmclean/mcp-zoo.git","type":"git","directory":"reflect-mcp"},"_npmVersion":"11.13.0","description":"Debug MCP server that echoes its launch command/args, env, and inbound HTTP headers via a single 'echo' tool.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"zod":"^4.3.6","express":"^5.2.1","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.33.2","devDependencies":{"tsx":"^4.21.0","typescript":"^6.0.3","@types/node":"^25.6.0","concurrently":"^9.2.1","@biomejs/biome":"^2.4.13","@types/express":"^5.0.6"},"_npmOperationalInternal":{"tmp":"tmp/reflect-mcp_0.0.4_1777659728520_0.4950426587210983","host":"s3://npm-registry-packages-npm-production"}},"0.0.5":{"name":"@calvinmclean/reflect-mcp","version":"0.0.5","author":{"name":"calvinmclean"},"license":"MIT","_id":"@calvinmclean/reflect-mcp@0.0.5","maintainers":[{"name":"calvinmclean","email":"calvinlmc@gmail.com"}],"homepage":"https://github.com/calvinmclean/mcp-zoo/tree/main/reflect-mcp#readme","bugs":{"url":"https://github.com/calvinmclean/mcp-zoo/issues"},"bin":{"reflect-mcp":"dist/index.js"},"dist":{"shasum":"b60f29526df2cf475e8f375efbf89aa0e6ce15c6","tarball":"https://registry.npmjs.org/@calvinmclean/reflect-mcp/-/reflect-mcp-0.0.5.tgz","fileCount":4,"integrity":"sha512-i8WvQabvPaak+JSEeBUaN4v7vJmCj4uQXlXVHrt+ixrxscapsNbfHCG/kSiaDOKQXRnJkjXgbRjtJBILHhi58g==","signatures":[{"sig":"MEUCIH4Xp6ZtWVRYFesTiarkNiQ+O6q+FLfvIaF7eqyrCuOpAiEAgCO3T6SxaJfZL9YTCecY2lufppZ2Fe7lB4UAN92oQEs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@calvinmclean%2freflect-mcp@0.0.5","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":38502},"main":"dist/index.js","pnpm":{"ignoredBuiltDependencies":["esbuild"]},"type":"module","engines":{"node":">=24"},"gitHead":"8302cbdf651ae9301aadf47503b6930a96576c87","scripts":{"dev":"tsx src/index.ts","lint":"biome check src","build":"tsc","clean":"rm -rf dist node_modules","tunnel":"cloudflared tunnel run --url http://localhost:8099 dev","lint:fix":"biome check --write src","typecheck":"tsc --noEmit","dev:tunnel":"concurrently -kn dev,tunnel -c blue,magenta \"pnpm dev\" \"pnpm tunnel\"","prepublishOnly":"pnpm build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a9d8f530-7d0f-4eeb-8ae5-dfa086240a71"}},"repository":{"url":"git+https://github.com/calvinmclean/mcp-zoo.git","type":"git","directory":"reflect-mcp"},"_npmVersion":"11.13.0","description":"Debug MCP server that echoes its launch command/args, env, and inbound HTTP headers via a single 'echo' tool.","directories":{},"_nodeVersion":"24.14.1","dependencies":{"zod":"^4.3.6","express":"^5.2.1","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.33.2","devDependencies":{"tsx":"^4.21.0","typescript":"^6.0.3","@types/node":"^25.6.0","concurrently":"^9.2.1","@biomejs/biome":"^2.4.13","@types/express":"^5.0.6"},"_npmOperationalInternal":{"tmp":"tmp/reflect-mcp_0.0.5_1777661061386_0.9797421998438003","host":"s3://npm-registry-packages-npm-production"}},"0.0.6":{"name":"@calvinmclean/reflect-mcp","version":"0.0.6","description":"Debug MCP server that echoes its launch command/args, env, and inbound HTTP headers via a single 'echo' tool.","license":"MIT","author":{"name":"calvinmclean"},"repository":{"type":"git","url":"git+https://github.com/calvinmclean/mcp-zoo.git","directory":"reflect-mcp"},"homepage":"https://github.com/calvinmclean/mcp-zoo/tree/main/reflect-mcp#readme","bugs":{"url":"https://github.com/calvinmclean/mcp-zoo/issues"},"type":"module","main":"dist/index.js","bin":{"reflect-mcp":"dist/index.js"},"engines":{"node":">=24"},"packageManager":"pnpm@10.33.2","scripts":{"build":"tsc","dev":"tsx src/index.ts","dev:tunnel":"concurrently -kn dev,tunnel -c blue,magenta \"pnpm dev\" \"pnpm tunnel\"","tunnel":"cloudflared tunnel run --url http://localhost:8099 dev","lint":"biome check src","lint:fix":"biome check --write src","typecheck":"tsc --noEmit","clean":"rm -rf dist node_modules","prepublishOnly":"pnpm build"},"dependencies":{"@modelcontextprotocol/sdk":"^1.29.0","express":"^5.2.1","zod":"^4.3.6"},"devDependencies":{"@biomejs/biome":"^2.4.13","@types/express":"^5.0.6","@types/node":"^25.6.0","concurrently":"^9.2.1","tsx":"^4.21.0","typescript":"^6.0.3"},"publishConfig":{"access":"public"},"pnpm":{"ignoredBuiltDependencies":["esbuild"]},"gitHead":"23380c914f9f44eaf259072633f2103a97ce4216","_id":"@calvinmclean/reflect-mcp@0.0.6","_nodeVersion":"24.14.1","_npmVersion":"11.13.0","dist":{"integrity":"sha512-aN+GtURu4rnDzueSmMTtiRDAJv2zm6X6w6NEe143hR/GVevdy32SHI3+pv9m/19opf+Vk4c1MVNYnTNu8IN7Mw==","shasum":"b370d8a30be0e55f3bd0de21d29d06c208b5bc6d","tarball":"https://registry.npmjs.org/@calvinmclean/reflect-mcp/-/reflect-mcp-0.0.6.tgz","fileCount":4,"unpackedSize":38904,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@calvinmclean%2freflect-mcp@0.0.6","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFh8obw5GzM/g3XRONqvq+MNMCBIkNpjN2XdazwyHC0HAiB3nuI3TTlq0z4JwGV1PHdw721o0jgmww7vfYwnCv9aRA=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a9d8f530-7d0f-4eeb-8ae5-dfa086240a71"}},"directories":{},"maintainers":[{"name":"calvinmclean","email":"calvinlmc@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/reflect-mcp_0.0.6_1777668279715_0.40403142590696084"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-01T18:13:02.663Z","modified":"2026-05-01T20:44:40.143Z","0.0.2":"2026-05-01T18:13:03.034Z","0.0.3":"2026-05-01T18:16:12.239Z","0.0.4":"2026-05-01T18:22:08.669Z","0.0.5":"2026-05-01T18:44:21.545Z","0.0.6":"2026-05-01T20:44:39.876Z"},"bugs":{"url":"https://github.com/calvinmclean/mcp-zoo/issues"},"author":{"name":"calvinmclean"},"license":"MIT","homepage":"https://github.com/calvinmclean/mcp-zoo/tree/main/reflect-mcp#readme","repository":{"type":"git","url":"git+https://github.com/calvinmclean/mcp-zoo.git","directory":"reflect-mcp"},"description":"Debug MCP server that echoes its launch command/args, env, and inbound HTTP headers via a single 'echo' tool.","maintainers":[{"name":"calvinmclean","email":"calvinlmc@gmail.com"}],"readme":"# @calvinmclean/reflect-mcp\n\nA debug MCP server with one tool — `echo` — that returns the command and args this process was launched with, the environment it sees, and (when running over HTTP) the inbound request headers.\n\nUseful for verifying [obot](https://github.com/obot-platform/obot) / [nanobot](https://github.com/nanobot-ai/nanobot) wiring end‑to‑end:\n\n- Are env vars from a catalog entry actually reaching the spawned MCP process?\n- Are env‑var bindings against a Kubernetes Secret being projected onto the right env var name?\n- Are HTTP headers (static, user‑supplied, or `secretBinding`-backed) propagated through nanobot to the upstream call?\n- Are extra args appended to `npxConfig.args` / `containerizedConfig.args` arriving in `process.argv`?\n\n## Quick start\n\n### From npm (recommended)\n\n```bash\n# stdio\nnpx @calvinmclean/reflect-mcp --transport stdio\n\n# streamable HTTP on :8099/mcp\nnpx @calvinmclean/reflect-mcp --transport http-streamable --port 8099 --path /mcp\n```\n\n### From the prebuilt container image\n\n```bash\ndocker run --rm -p 8099:8099 \\\n  ghcr.io/calvinmclean/mcp-zoo/reflect-mcp:main\n```\n\nThe default `CMD` runs the streamable HTTP transport on `:8099/mcp`. Override `CMD` to use stdio:\n\n```bash\ndocker run --rm -i ghcr.io/calvinmclean/mcp-zoo/reflect-mcp:main --transport stdio\n```\n\n### From source\n\n```bash\ngit clone https://github.com/calvinmclean/mcp-zoo\ncd mcp-zoo/reflect-mcp\npnpm install\npnpm run build\nnode dist/index.js --transport stdio\n```\n\n## Usage\n\n```text\nreflect-mcp [--transport http-streamable|stdio] [--port N] [--path /mcp] [<arbitrary extra args...>]\n\n  --transport   Transport to use. Default: http-streamable.\n  --port        TCP port for http-streamable. Default: 8099.\n  --path        URL path for the MCP endpoint. Default: /mcp.\n\nAny other arguments are passed through unchanged and surfaced verbatim\nvia the `echo` tool's `argv` and `extraArgs` fields. This makes\nreflect-mcp useful for verifying how nanobot/obot construct argv when\nlaunching MCP servers (e.g. catalog `npxConfig.args`).\n```\n\n## The `echo` tool\n\nOne optional parameter, `key`. The response shape depends on the transport:\n\n| Transport | `argv` + metadata | `headers` | `env` |\n|---|---|---|---|\n| **streamable HTTP** | ✓ | ✓ (redacted by default) | omitted |\n| **stdio**           | ✓ | omitted (stdio has none) | ✓ (redacted by default) |\n\n`env` is **never** returned over the streamable HTTP transport — exposing it on a network-reachable endpoint would be a casual exfiltration vector even with redaction (the key names alone reveal which credentials are set). Use the stdio variant to inspect env. `headers` are gated by the `key` over HTTP since they routinely carry `Authorization` tokens and the like.\n\n### HTTP, default (headers redacted, no env)\n\n```jsonc\n// tools/call → { \"name\": \"echo\", \"arguments\": {} }\n{\n  \"argv\": [\n    \"/usr/bin/node\",\n    \"/app/dist/index.js\",\n    \"--transport\", \"http-streamable\",\n    \"--port\", \"8099\",\n    \"--path\", \"/mcp\",\n    \"--reflect-tag=containerized\",\n    \"hello-from-catalog\"\n  ],\n  \"extraArgs\": [\"--reflect-tag=containerized\", \"hello-from-catalog\"],\n  \"recognizedFlags\": { \"transport\": \"http-streamable\", \"port\": 8099, \"path\": \"/mcp\" },\n  \"execPath\": \"/usr/bin/node\",\n  \"cwd\": \"/app\",\n  \"pid\": 1,\n  \"nodeVersion\": \"v24.x.x\",\n  \"platform\": \"linux\",\n  \"arch\": \"amd64\",\n  \"transport\": \"http-streamable\",\n  \"redacted\": true,\n  \"receivedAt\": \"2026-04-28T12:34:56.789Z\",\n  \"headers\": {\n    \"host\":            \"********\",\n    \"x-reflect-test\":  \"********\",\n    \"x-reflect-token\": \"********\",\n    \"authorization\":   \"********\"\n  }\n  // no `env` field over HTTP\n}\n```\n\n### HTTP, with the key (headers unredacted, still no env)\n\nAt server startup, stderr prints something like:\n\n```\n═══════════════════════════════════════════════════════════════════════\nreflect-mcp echo key: 7c2e1f4a-3b9d-4e8c-9a51-1f6b2d3a4c5e\n\nPass this as the `key` argument to the echo tool to receive UNREDACTED\nenv vars and HTTP headers. Without it, values are replaced with '********'.\n═══════════════════════════════════════════════════════════════════════\n```\n\nThen:\n\n```jsonc\n// tools/call → { \"name\": \"echo\", \"arguments\": { \"key\": \"7c2e1f4a-...\" } }\n{\n  // ...same shape as above, but with real header values; still no env...\n  \"headers\": {\n    \"authorization\":   \"Bearer eyJhbG...\",\n    \"x-reflect-test\":  \"hello-from-obot\",\n    \"x-reflect-token\": \"actual-token\",\n    \"host\":            \"reflect-mcp-abc12.obot-mcp.svc:80\"\n  },\n  \"redacted\": false\n}\n```\n\n### stdio, with the key (env unredacted)\n\n```jsonc\n// tools/call → { \"name\": \"echo\", \"arguments\": { \"key\": \"7c2e1f4a-...\" } }\n{\n  // ...same metadata fields as HTTP...\n  \"transport\": \"stdio\",\n  \"redacted\": false,\n  \"env\": {\n    \"DD_API_KEY\":          \"abc123def456\",   // ← projected via valueFrom.secretKeyRef\n    \"REFLECT_TEST_SECRET\": \"actual-secret\",\n    \"REFLECT_TEST_VALUE\":  \"hello\",\n    \"PATH\":                \"/usr/local/bin:/usr/bin:/bin\"\n  }\n  // no `headers` field on stdio\n}\n```\n\n### Field notes\n\n- **`argv`** — captured at process startup, byte-for-byte. Never redacted (redacting would defeat the point of verifying how nanobot/obot constructs argv). Don't put secrets in argv.\n- **`extraArgs`** — convenience field listing the args that were *not* consumed by `--transport` / `--port` / `--path`.\n- **`recognizedFlags`** — what reflect-mcp actually parsed.\n- **`env`** — full `process.env`. **Only present on stdio.** Includes whatever obot's runner injected via `envFrom` and whatever kubelet projected via `valueFrom.secretKeyRef`. Values redacted by default.\n- **`headers`** — inbound HTTP request headers. **Only present on streamable HTTP.** Read from `extra.requestInfo.headers` per [SDK migration docs](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/migration.md). Values redacted by default.\n- **`redacted`** — `true` when the conditional field (env on stdio, headers on HTTP) has been redacted; `false` when the key matched and real values are returned. Lets the caller distinguish \"received `********`\" from \"the value is literally `********`.\"\n\n## Health check\n\nWhen running the HTTP transport, `GET /healthz` returns `{ \"ok\": true }` (port `8099` by default). Stdio mode has no health endpoint.\n\n## Catalog integration\n\nThree obot catalog entries shipped from this repo's [`catalog.yaml`](../catalog.yaml):\n\n```yaml\n# npx, single-user, stdio\n- runtime: npx\n  npxConfig:\n    package: \"@calvinmclean/reflect-mcp\"\n    args: [--transport, stdio, --reflect-tag=npx-single-user, hello-from-catalog]\n  env:\n    - { key: REFLECT_TEST_VALUE,  required: false, sensitive: false }\n    - { key: REFLECT_TEST_SECRET, required: false, sensitive: true  }\n\n# containerized, multi-user, HTTP\n- runtime: containerized\n  containerizedConfig:\n    image: ghcr.io/calvinmclean/mcp-zoo/reflect-mcp:main\n    port: 8099\n    path: /mcp\n    args: [--transport, http-streamable, --port, \"8099\", --path, /mcp, --reflect-tag=containerized, hello-from-catalog]\n  env:\n    - { key: REFLECT_TEST_VALUE,  required: false, sensitive: false }\n    - { key: REFLECT_TEST_SECRET, required: false, sensitive: true  }\n\n# remote, hosted\n- runtime: remote\n  remoteConfig:\n    fixedURL: https://reflect.scrat.hale.sh/mcp\n    headers:\n      - { key: X-Reflect-Test,  value: hello-from-obot, sensitive: false }  # static\n      - { key: X-Reflect-Token, sensitive: true }                            # user-supplied\n```\n\nThe remote variant is also a good place to attach a `secretBinding` to verify the external-secrets feature against a pre-existing Kubernetes Secret.\n\n## Build & develop\n\nRequires Node `>=24` and pnpm (managed via the `packageManager` field — corepack will install the right version).\n\n```bash\npnpm install              # install deps\npnpm dev                  # tsx src/index.ts (no build step)\npnpm build                # tsc → dist/\npnpm lint                 # biome check\npnpm lint:fix             # biome check --write (lint + format auto-fix)\npnpm typecheck            # tsc --noEmit\npnpm clean                # rm -rf dist node_modules\n```\n\nTo wipe the lockfile too (rare): `pnpm clean && rm -f pnpm-lock.yaml && pnpm install`.\n\n### Local dev behind a Cloudflare tunnel\n\nFor testing the HTTP transport against a real public URL (e.g. so an obot/nanobot instance running elsewhere can reach it), reflect-mcp ships scripts for [cloudflared](https://github.com/cloudflare/cloudflared) integration.\n\n**One-time setup** (per environment, idempotent):\n\n```bash\nbrew install cloudflared          # or your package manager\ncloudflared tunnel login          # authenticate\ncloudflared tunnel create dev   # if not already created\ncloudflared tunnel route dns --overwrite-dns dev reflect.hale.sh\n```\n\n**Day-to-day**:\n\n```bash\npnpm dev:tunnel    # runs `dev` + `tunnel` side-by-side via concurrently\n                   # (named colored prefixes; ctrl-C kills both)\n```\n\nOr run the two halves independently:\n\n```bash\npnpm dev           # terminal 1: tsx server on :8099\npnpm tunnel        # terminal 2: cloudflared → http://localhost:8099\n```\n\nOnce it's up, hit `https://reflect.hale.sh/mcp` from any MCP client.\n\n### Publishing to npm\n\nCI publishes via [npm Trusted Publishers](https://docs.npmjs.com/trusted-publishers) (OIDC) on push to `main` — see [`.github/workflows/reflect-mcp-npm.yaml`](../.github/workflows/reflect-mcp-npm.yaml). To release: bump `version` in `package.json` and merge to `main`. Local dry-run:\n\n```bash\npnpm lint && pnpm typecheck && pnpm build\nnpm publish --dry-run --access public\n```\n\n### Container image\n\nThe Dockerfile is two-stage:\n\n- **build**: `cgr.dev/chainguard/node:latest-dev` (Node 24, has shell + apk for the build).\n- **runtime**: `cgr.dev/chainguard/node:latest` (distroless — Node + libc only). Runs as the built-in `nonroot` user (uid 65532).\n\n```bash\n# Local build (current arch only, loaded into Docker)\ndocker buildx build --load -t reflect-mcp:local .\ndocker run --rm -p 8099:8099 reflect-mcp:local\n\n# Multi-arch build, pushed to a registry (can't `--load` multi-arch into the\n# local Docker image store; push it instead)\ndocker buildx create --name multiarch --use --bootstrap   # one-time\ndocker buildx build \\\n  --platform linux/amd64,linux/arm64 \\\n  -t ghcr.io/calvinmclean/mcp-zoo/reflect-mcp:dev \\\n  --push .\n```\n\n## Security note\n\n`echo` returns `process.env` and inbound HTTP headers, redacted with `********` by default. The per-process echo key (random UUID, regenerated on every restart, printed on stderr at startup) gates unredacted access. The trust model:\n\n- **Anyone who can call `echo` without the key** sees the *names* of env vars and headers, but every value is `********`. Useful for confirming \"is `DD_API_KEY` reaching this pod?\" without exposing the value itself.\n- **Anyone who can read the server's stderr** can call `echo` with the key and get real values. In a Kubernetes deployment that means anyone with `kubectl logs` on the pod.\n\nTwo implications:\n\n1. The key is ephemeral — restart the pod, key changes. There's no way to embed a fixed key. Intentional: makes \"I leaked stderr to a log shipper\" recoverable by restarting.\n2. This is still a *debug* tool. Even with redaction in place, `echo` reveals which env-var keys are set, which headers are inbound, and what argv the process sees. Don't expose reflect-mcp to anyone you wouldn't show your `kubectl describe pod` output to.\n\n## License\n\nMIT.\n","readmeFilename":"README.md"}