{"_rev":"6-9170262499850f6639392a700b1cf012","time":{"created":"2026-09-10T06:30:59.445Z","modified":"2026-09-10T06:31:00.221Z","0.1.0":"2026-09-10T06:22:12.618Z","0.1.1":"2026-09-10T06:25:38.590Z","0.1.2":"2026-09-10T06:30:59.974Z"},"_id":"@linkedroid/mcp-server","name":"@linkedroid/mcp-server","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.2":{"name":"@linkedroid/mcp-server","version":"0.1.2","description":"MCP server exposing Linkedroid's LinkedIn tools to Claude and other MCP clients","homepage":"https://www.linkedroid.com","type":"module","bin":{"linkedroid-mcp":"src/index.js"},"main":"./src/index.js","scripts":{"start":"node src/index.js","test":"node test/e2e-test.mjs"},"dependencies":{"@modelcontextprotocol/sdk":"^1.30.0"},"engines":{"node":">=20"},"publishConfig":{"access":"public"},"keywords":["mcp","modelcontextprotocol","linkedin","linkedroid"],"license":"UNLICENSED","_id":"@linkedroid/mcp-server@0.1.2","gitHead":"1428173c905c2a7ebdec84db1613afe639bf2923","_nodeVersion":"22.23.2","_npmVersion":"10.9.8","dist":{"integrity":"sha512-nm+R6BDo9BwnfcSs2MeaJu0stw/wDpK1wsTO9XNbA3S39YsFEbmdF/irrIGozH1RIeKx68yI2XfstdzrzPstuw==","shasum":"bb0163b4991c84192bb8bb58b687cf7fb373a4a9","tarball":"https://registry.npmjs.org/@linkedroid/mcp-server/-/mcp-server-0.1.2.tgz","fileCount":5,"unpackedSize":21604,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCLYcuLNY9huMcXKHhBvyfbRaqzqAy5UERY2DO58srguwIhAJZXjPWKjGtxakKXDH8sT8wzymchrJwb6EYvKZpNpkEs"}]},"_npmUser":{"name":"linkedroid","email":"help@linkedroid.com"},"directories":{},"maintainers":[{"name":"linkedroid","email":"help@linkedroid.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mcp-server_0.1.2_1789021859835_0.3065766298295105"},"_hasShrinkwrap":false}},"maintainers":[{"name":"linkedroid","email":"help@linkedroid.com"}],"description":"MCP server exposing Linkedroid's LinkedIn tools to Claude and other MCP clients","homepage":"https://www.linkedroid.com","keywords":["mcp","modelcontextprotocol","linkedin","linkedroid"],"license":"UNLICENSED","readme":"# @linkedroid/mcp-server\n\nExposes Linkedroid's LinkedIn tools to any MCP client — Claude, OpenClaw, Cursor,\nor anything else that speaks the protocol.\n\n```\nMCP client  ──stdio──►  linkedroid-mcp  ──https──►  Linkedroid API\n                                                     │  job queue\n                                                     ▼\n                                          your Chrome extension\n                                                     │\n                                                     ▼\n                                                 LinkedIn\n```\n\nThe server holds no LinkedIn session. It queues a job; the extension in *your*\nbrowser claims it, runs it against your live LinkedIn session, and reports back.\nClose the browser and calls queue rather than fail — they run when it reconnects.\n\n## Install\n\nNothing to install. Clients run it with `npx`, which fetches the package on\nfirst use. Requires Node 20 or newer.\n\nFor local development of the server itself:\n\n```bash\ncd packages/mcp-server && npm install && npm test\n```\n\n## Get a token\n\nIn the Linkedroid extension: **Settings → Agent Access → Generate token.**\n\nGive it a name you will recognise later (\"Laptop — OpenClaw\"), and copy it — it\nis shown once and the server stores only a hash, so it cannot be recovered.\n\nAn agent token is not your session token. It reaches only `/agent/*` routes —\nnever account or billing — expires after 90 days, and can be revoked from the\nsame screen without disturbing your browser session. Revoking takes effect\nimmediately.\n\n## Configure your client\n\nThe extension's token screen shows a ready-to-paste config with your token\nalready in it. Every client needs the same three things: the **command**\n(`npx`), the **package** (`@linkedroid/mcp-server`), and `LINKEDROID_TOKEN` in\nits **env**. Two concrete examples follow; other clients use the same values in\ntheir own config format.\n\n**Claude Code**\n\n```bash\nclaude mcp add linkedroid \\\n  --env LINKEDROID_TOKEN=lrd_... \\\n  -- npx -y @linkedroid/mcp-server\n```\n\n**Claude Desktop** — in `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"linkedroid\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@linkedroid/mcp-server\"],\n      \"env\": { \"LINKEDROID_TOKEN\": \"lrd_...\" }\n    }\n  }\n}\n```\n\n| Env var | Default | Purpose |\n|---|---|---|\n| `LINKEDROID_TOKEN` | *(required)* | Your Linkedroid JWT |\n| `LINKEDROID_API_BASE` | `https://api.linkedroid.com/api/v1/linkedroid` | Point at a local API for development |\n| `LINKEDROID_TIMEOUT_MS` | `180000` | Per-call budget. Generous on purpose: a sleeping browser may need to wake and open a tab |\n\n## Tools\n\n**Nothing about the tool set is hardcoded here.** `tools/list` is served from\n`GET /agent/tools`, so adding a tool to `apis/agent/tools.json` makes it\navailable to every client with no change to this package and no reinstall.\n\nWhich tools appear depends on `AGENT_ENABLED_TIERS` on the server:\n\n| Tier | Behaviour |\n|---|---|\n| `read` | Runs immediately. LinkedIn reads only |\n| `author` | Runs immediately. Writes campaign/flow state, never contacts LinkedIn |\n| `act` | **Queued for human approval.** Nothing is sent until the user approves it in the extension |\n\nAn `act` call returns *\"queued and needs the user's approval\"* — not an error.\nReport that back to the user rather than retrying.\n\n**There is deliberately no approval tool.** An MCP tool that approves jobs would\nlet the model authorise its own sends, which is the one thing the tier system\nexists to prevent. Approval happens in the extension UI, by a person.\n\n## Troubleshooting\n\n| Symptom | Cause |\n|---|---|\n| `Could not load Linkedroid tools: … LINKEDROID_TOKEN` | Token missing or expired — copy a fresh one |\n| Tools list is empty | `AGENT_ENABLED_TIERS` is unset or empty server-side |\n| `did not finish within Ns` | Browser closed, asleep, or not signed in. The job stays queued and runs when the extension reconnects |\n| `NOT_ON_LINKEDIN_TAB` | No LinkedIn tab open. Retryable — the extension opens one, but a locked or slow browser can miss the window |\n| `TIER_NOT_ENABLED` | Tool exists but its tier is off server-side |\n\nDiagnostics go to **stderr**; stdout is the JSON-RPC channel. In Claude Code,\n`claude mcp list` shows connection status.\n\n## Layout\n\n| File | Purpose |\n|---|---|\n| `src/client.js` | HTTP + the enqueue/poll round trip. Returns tagged outcomes, never throws for expected conditions |\n| `src/server.js` | MCP wiring. Maps outcomes onto tool results |\n| `src/index.js` | stdio entry point and env handling |\n\n`client.js` and `server.js` take injected dependencies, so both are testable\nwithout spawning anything.\n\n## Tests\n\n```bash\n# in apis/:  docker compose up -d\nnpm test\n```\n\nDrives the full stack with a real MCP client over a real stdio pipe: this server\nas a spawned child process, the Linkedroid API booted in-process against a local\nthrowaway database, and the **real extension poller** from `linkedroid-v2`\ndraining the queue. Only the content script and auth-cookie relay are faked —\nthere is no LinkedIn session under Node.\n\nCovers the handshake, dynamic tool listing, a full round trip, argument shaping,\nresponse projection, schema violations, unknown tools, `act`-tier approval\ngating (including asserting the message is *not* sent before approval, and *is*\nafter), extension-offline reporting, and bad-token messaging.\n\nRefuses to run unless `MONGODB_URI` is local, and asserts the live connection\nhost before writing.\n\n## Reverting\n\nEverything is on `feat/agent-mcp-server`, and this package is entirely new —\nno existing file was modified.\n\n```bash\ngit checkout feat/agent-job-queue      # keep the API, drop the MCP server\nrm -rf packages/mcp-server\n```\n\n`pnpm-workspace.yaml` already globs `packages/*`, so nothing was registered by\nhand and nothing needs unregistering.\n","readmeFilename":"README.md"}