{"_id":"@byliner/mcp-approver","name":"@byliner/mcp-approver","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@byliner/mcp-approver","version":"0.1.0","type":"module","bin":{"byliner-mcp-approver":"dist/server.js"},"scripts":{"start":"tsx src/server.ts","dev":"tsx watch src/server.ts","inspect":"npx @modelcontextprotocol/inspector tsx src/server.ts","typecheck":"tsc --noEmit","build":"rm -rf dist && tsc -p tsconfig.build.json","prepack":"npm run build"},"dependencies":{"@modelcontextprotocol/sdk":"^1.29.0","dotenv":"^16.4.5","zod":"^3.23.8"},"devDependencies":{"@types/node":"^22.9.0","tsx":"^4.19.2","typescript":"^5.6.3"},"description":"MCP server letting a human approver read their Byliner approval queue. Read-only — decisions are made in the dashboard, never through a tool call.","keywords":["mcp","modelcontextprotocol","byliner","approval","human-in-the-loop","review"],"license":"MIT","homepage":"https://byliner.dev","repository":{"type":"git","url":"git+https://github.com/byliner-dev/byliner.git","directory":"apps/mcp-approver"},"engines":{"node":">=20"},"publishConfig":{"access":"public"},"bugs":{"url":"https://github.com/byliner-dev/byliner/issues"},"_id":"@byliner/mcp-approver@0.1.0","gitHead":"32cb9c19fa39132f814455c98431b56fcc4d2d1b","_nodeVersion":"22.12.0","_npmVersion":"10.9.0","dist":{"integrity":"sha512-6Tzf4qlF3TdEVzciZFroTDh84ODi73qefFqjYeFD3as5uWqeLT5CbAX5VyD7iALlkg6vv7HxkUwObjk0BKpqtw==","shasum":"f78bbb5ddabd3b265aa2abe4d6671aaa9881aee5","tarball":"https://registry.npmjs.org/@byliner/mcp-approver/-/mcp-approver-0.1.0.tgz","fileCount":14,"unpackedSize":34562,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDBfoobO78pjrckIp7iuUcM4c6+igSNC6EU+wb9yQn0BQIgJJsZu/bfF+Ws5v3AzUV7Q0CVeLS1VQM+vMBHi0UmeF4="}]},"_npmUser":{"name":"aarontropy","email":"aarontropy@gmail.com"},"directories":{},"maintainers":[{"name":"aarontropy","email":"aarontropy@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mcp-approver_0.1.0_1784491732121_0.9488321211634392"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-19T20:08:51.962Z","0.1.0":"2026-07-19T20:08:52.262Z","modified":"2026-07-19T20:08:52.463Z"},"maintainers":[{"name":"aarontropy","email":"aarontropy@gmail.com"}],"description":"MCP server letting a human approver read their Byliner approval queue. Read-only — decisions are made in the dashboard, never through a tool call.","homepage":"https://byliner.dev","keywords":["mcp","modelcontextprotocol","byliner","approval","human-in-the-loop","review"],"repository":{"type":"git","url":"git+https://github.com/byliner-dev/byliner.git","directory":"apps/mcp-approver"},"bugs":{"url":"https://github.com/byliner-dev/byliner/issues"},"license":"MIT","readme":"# `@byliner/mcp-approver`\n\nMCP server that lets a human approver see what is waiting on them.\n\nA thin adapter over the Byliner Engine API — it contains no business logic.\nSee [`docs/mcp-servers.md`](../../docs/mcp-servers.md) for the design rationale\nshared by both servers.\n\n## Tools\n\n| Tool | Engine call |\n|---|---|\n| `list_pending_approvals` | `GET /v1/approval-requests?scope=actionable&view=summary` |\n| `get_approval_detail` | `GET /v1/approval-requests/{id}?view=summary` |\n\nBoth are read-only, and both return the engine's **summary projection** rather\nthan the raw submitted payload: `subject.data` never crosses this boundary. That\nrule lives in the engine (`?view=summary`), not in client-side truncation, so it\nis applied identically to every consumer.\n\n## Read-only by design\n\n**There is no `approve`, `reject`, or `delegate` tool, and there will not be\none.** A decision carries human authority and must be attributed to a verified\nperson acting deliberately. A tool call reaching this process proves only that\n*something* connected to it, so routing a decision through here would attribute\na binding sign-off to whatever held the session.\n\nFor the same reason there is no elicitation-based `approve? y/n` prompt —\nelicitation resolves to whoever is present in the current session.\n\nDecisions happen in the dashboard, where the actor authenticates directly.\n\nBoth tools return a **`review_url`** — a dashboard link naming the request. It\ncarries no credential: whoever follows it signs in as themselves and the engine\ndecides what they may do. That is the handoff from a surface that cannot record\na decision to one that can.\n\nThe widget-session-token version of this (`open_approval`, authorising a\nspecific actor without a sign-in, for embedding in someone else's application)\nis still deferred — see `docs/mcp-servers.md` §3.\n\n## Scoping\n\n`list_pending_approvals` returns only requests actionable by *this server's\nconfigured identity* — never the whole org's queue. \"Actionable\" mirrors the\nsame rules the engine enforces at decision time (initiator ≠ approver, one vote\nper actor), so the queue never lists a request that would be refused on submit.\n\nNo tool takes an actor parameter. The scope query names a *mode*\n(`scope=actionable`), and the engine resolves the actor from the acting\ncontext, so there is no parameter through which one caller could request\nanother person's queue.\n\n## Environment variables\n\n| Var | Required | Default | Purpose |\n|---|---|---|---|\n| `BYLINER_APPROVER_HANDLE` | **yes** | — | Email of the human this instance acts as. |\n| `BYLINER_APPROVER_PASSWORD` | **yes** | — | That human's password. Seeded demo value: `demo`. |\n| `BYLINER_API_URL` | no | `http://localhost:4000` | Engine API base URL. |\n| `BYLINER_DASHBOARD_URL` | no | same as `BYLINER_API_URL` | Where `review_url` points. The deployed demo serves the dashboard and API from one origin, so the default is right there; local development needs `http://localhost:5173`. |\n| `BYLINER_TIMEOUT_MS` | no | `10000` | Per-request timeout. |\n\nSeeded handles: `priya@byliner.demo`, `marcus@byliner.demo`,\n`dana@byliner.demo`, `sam@byliner.demo`.\n\nThe server signs in at startup, so bad credentials fail immediately with a\nclear stderr message rather than surfacing as a confusing 401 on the first tool\ncall. Sessions are re-established automatically if one expires mid-run.\n\n### Password-in-env is the remaining demo simplification\n\nIdentity is now real: the credentials are exchanged for a session token the\nengine verifies, and no header names the actor. What is still not\nproduction-shaped is *how this process obtains* that session.\n\nA production build uses an **MCP OAuth 2.1 resource-server flow**: the\nconnecting client presents a token from the org's federated IdP, this server\nvalidates it against that environment's registered `IdpConfig` (issuer + JWKS,\nmandatory audience check), and the approver identity comes from the verified\ntoken's claims. This process would never handle a password at all — holding a\nuser's password in an env var is precisely what OAuth exists to avoid.\n\n## Running\n\n```bash\n# From the repo root, with the engine running on :4000\npnpm --filter @byliner/api dev\n\n# Then, in another shell:\nBYLINER_APPROVER_HANDLE=priya@byliner.demo BYLINER_APPROVER_PASSWORD=demo \\\n  pnpm --filter @byliner/mcp-approver start\n```\n\nExercise the tools with the MCP Inspector:\n\n```bash\ncd apps/mcp-approver\nBYLINER_APPROVER_HANDLE=priya@byliner.demo BYLINER_APPROVER_PASSWORD=demo pnpm inspect\n```\n\n## Claude Desktop config\n\nAdd to `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"byliner-approver\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@byliner/mcp-approver\"],\n      \"env\": {\n        \"BYLINER_APPROVER_HANDLE\": \"priya@byliner.demo\",\n        \"BYLINER_APPROVER_PASSWORD\": \"demo\",\n        \"BYLINER_API_URL\": \"http://localhost:4000\"\n      }\n    }\n  }\n}\n```\n\nRun one instance per approver persona if you want to demo more than one queue;\neach needs its own server entry with its own handle.\n\nNo checkout required — `npx` fetches the published package. To run against a\nlocal checkout instead, use `[\"tsx\", \"/absolute/path/to/apps/mcp-approver/src/server.ts\"]`.\n\nThe transport is stdio, so the server logs to stderr only; anything written to\nstdout would corrupt the protocol stream.\n","readmeFilename":"README.md","_rev":"1-91fd8b36a173a0d794eacf8500abbeba"}