@linkbunny/mcp 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,28 +2,97 @@
2
2
 
3
3
  A local stdio MCP server for owner-authorized LinkBunny workflows. Requires Node.js 22 or later and an account API token supplied as `LINKBUNNY_API_TOKEN` in the process environment.
4
4
 
5
+ These examples pin the published `0.1.1` release.
6
+
5
7
  ```sh
6
- npx -y @linkbunny/mcp@0.1.0
8
+ npx -y @linkbunny/mcp@0.1.1
7
9
  ```
8
10
 
9
11
  Your MCP client starts this process and communicates over stdin/stdout. Running the command alone waits for protocol input. npm hosts package files; no LinkBunny MCP process is hosted. API requests go only to `https://api.getlinkbunny.com/v1` over HTTPS. There is no endpoint override, daemon, polling, automatic retry, publication, or tool chaining.
10
12
 
11
13
  ## Configure a client
12
14
 
13
- See [client setup, upgrades, and removal](https://getlinkbunny.com/docs/agents#mcp) for pinned Codex, Claude Code, and VS Code examples. Inject the token from an owner-approved secret store into the client environment. Do not place tokens in command arguments, chat, logs, screenshots, or tracked configuration. Restart the MCP process after rotating the token.
15
+ See [the copyable MCP setup prompt and client examples](https://getlinkbunny.com/docs/agents#mcp). The MCP prompt is separate from the skill installation prompt. Follow preparation → owner authorization for the exact persistent config bundle → registration → native trust/restart → fresh-session discovery.
16
+
17
+ An agent can inspect the project and prepare registration. Before writing persistent client configuration, it must show the owner the exact client, scope, config paths/diff, executable/version, secret-loading path, and trust/restart steps, then request one explicit authorization for that bundle. Preserve unrelated configuration and keep native trust controls enabled.
18
+
19
+ Locate the canonical production `.env.linkbunny` from project documentation and deployment metadata without displaying secrets; ask the owner if its identity is unclear. `.env.linkbunny` alone is not registration and does not update an already-running client's environment. Keep the file private and untracked. Never put its token in chat, command arguments, logs, screenshots, or tracked configuration.
14
20
 
15
- For Codex, add this to your configuration and launch Codex with the token already in its environment:
21
+ For [Codex](https://developers.openai.com/codex/mcp), after authorization merge this into the trusted project's `.codex/config.toml`. Check the active Codex home and same-name entries in other scopes. Replace the example path with the reviewed absolute path on the MCP host:
16
22
 
17
23
  ```toml
18
24
  [mcp_servers.linkbunny]
19
25
  command = "npx"
20
- args = ["-y", "@linkbunny/mcp@0.1.0"]
21
- env_vars = ["LINKBUNNY_API_TOKEN"]
26
+ args = ["-y", "@linkbunny/mcp@0.1.1", "--token-file", "/absolute/project/.env.linkbunny"]
22
27
  ```
23
28
 
29
+ Run `codex mcp list` from the intended project to check registration. Have the owner complete native trust, restart the client, and begin a fresh backing session in Codex or Agent Control before discovery. Same-process hot reload is not proven. Use `/mcp` to check the active server.
30
+
31
+ For [Claude Code](https://code.claude.com/docs/en/mcp), after authorization merge this into the project's `.mcp.json`. Replace the example path with the reviewed absolute secret-file path. Have the owner complete native trust, restart Claude Code, and begin a fresh session before discovery:
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "linkbunny": {
37
+ "type": "stdio",
38
+ "command": "npx",
39
+ "args": [
40
+ "-y",
41
+ "@linkbunny/mcp@0.1.1",
42
+ "--token-file",
43
+ "/absolute/project/.env.linkbunny"
44
+ ]
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ This registers project scope. Local and user scope entries are in `~/.claude.json`; local entries belong to a project path. From the intended project, run `claude mcp get linkbunny`, then use `/mcp` in Claude Code to check status and approve the project server. `claude mcp get` may start a stdio server as a health check and does not require workspace trust. Check same-name entries in other scopes if the loaded command differs. Keep diagnostic output private.
51
+
52
+ For [VS Code](https://code.visualstudio.com/docs/agents/reference/mcp-configuration), after authorization merge this into `.vscode/mcp.json` for workspace scope:
53
+
54
+ ```json
55
+ {
56
+ "servers": {
57
+ "linkbunny": {
58
+ "type": "stdio",
59
+ "command": "npx",
60
+ "args": ["-y", "@linkbunny/mcp@0.1.1"],
61
+ "envFile": "${workspaceFolder}/.env.linkbunny"
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ VS Code supports direct `envFile` loading. Unlike the package loader, it can load additional variables from that file; review the variable names privately before choosing this configuration. The private, untracked file and Node/npm must be available on the host running MCP, including SSH, containers, remote workspaces, or Agent Host. A local file is not automatically available remotely; do not transfer secrets implicitly.
68
+
69
+ Have the owner complete workspace/server trust. Use `MCP: List Servers` to start/restart LinkBunny, then begin a fresh chat and inspect tools without invoking them. Use `MCP: Browse Resources` to read the guidance resource.
70
+
71
+ ## Load only the token without a shell
72
+
73
+ The Codex and Claude examples use the reviewed loader in this release, avoiding a project shell wrapper. `--token-file` accepts one absolute path to a regular file up to 64 KiB. The loader reads only `LINKBUNNY_API_TOKEN`; it does not source the file, evaluate shell content, expand variables, or load other entries into the environment. Use single-line assignments throughout the file; multiline values and shell statements are rejected.
74
+
75
+ Use one single-line assignment, optionally prefixed with `export`, with an unquoted, single-quoted, or double-quoted literal token and an optional trailing comment. The token must contain 32–512 letters, digits, or `._~-` characters. Duplicate, missing, empty, malformed, interpolated, or command-substituted token entries fail closed. Errors never print values.
76
+
77
+ An explicit file takes precedence over an inherited token; invalid files never fall back to that token. Without the flag, the server still accepts a token supplied through an approved process environment. Restart the MCP process after token rotation.
78
+
79
+ ## Verify safely
80
+
81
+ A package cached or downloaded by npm is not proof that a server is registered. Check initialization separately after saving the registration. Tokens saved in a secret store or repository environment file are not automatically loaded by the client.
82
+
83
+ In the fresh session, ask the configured client to perform only `initialize`, `tools/list`, `resources/list`, and `resources/read` for `linkbunny://guidance/skill`. Expect server version `0.1.1`, exactly the five tools below, and the guidance text. Do not call any workflow tool to test configuration: even `check_in` can reserve an assignment. If a client cannot list or read resources, report that part as unverified; reading the web skill does not verify MCP resource access.
84
+
85
+ Discovery and resource reads make no LinkBunny API requests. Startup validates token format locally, so these checks cannot prove API token admission, subscription, or entitlement. For isolated discovery-only testing, use the synthetic example `LINKBUNNY_API_TOKEN=synthetic-discovery-only-not-a-real-token-00000000`. It passes the local format check but cannot authorize real API activity. Never substitute a production token merely to test package startup. Discovery-only tests must not invoke `create_profile`, `update_profile`, `check_in`, `report_assignment`, or `reject_assignment`.
86
+
87
+ The completed MyOG.social check of released `@linkbunny/mcp@0.1.0` passed stdio initialization, discovery of exactly the five tools, and the resource read with a synthetic token. Stdout contained valid JSON-RPC, stderr was empty, shutdown was clean, and no mutation tool was called. The checked active Codex, Claude, repository, and common client scopes had no LinkBunny registration. Its existing production token was not loaded, and admission with its configured token was not verified.
88
+
24
89
  ## Tools and guidance
25
90
 
26
- Read the resource `linkbunny://guidance/skill` before using tools. It is the byte-identical copy of the canonical [LinkBunny skill](https://getlinkbunny.com/skill.md) captured at package build time. This README documents installation; the resource remains the authored behavioral authority. Newer guidance requires a reviewed package upgrade.
91
+ Read the resource `linkbunny://guidance/skill` before using tools. It is the byte-identical snapshot of the canonical [LinkBunny skill](https://getlinkbunny.com/skill.md) captured at package build time. `frontend/public/skill.md` is the single authored workflow contract; this README explains setup, not a separate set of workflow rules.
92
+
93
+ The released MCP resource stays pinned with the package and does not fetch the live skill URL. The live canonical skill can change independently. A separately installed local LinkBunny agent skill is another copy managed by its installer: updating MCP does not refresh that skill, and installing a skill does not register MCP.
94
+
95
+ In the MyOG.social comparison, the released `0.1.0` resource matched the live canonical skill at the time. The separately installed local skill had an outdated Domain Rating rule and lacked released additions. Consult the canonical skill for current workflow rules; a released snapshot may become stale.
27
96
 
28
97
  | Tool | API operation |
29
98
  | ------------------- | --------------------------------------- |
@@ -39,7 +108,11 @@ Results include validated API `data` or an `error` with a bounded `code` and sec
39
108
 
40
109
  ## Upgrade or remove
41
110
 
42
- Choose a reviewed release and replace the exact version in your client's arguments, then restart the server. Do not replace the pin with `latest` or an unversioned command. For Codex use `codex mcp remove linkbunny`; for Claude Code use `claude mcp remove linkbunny`; for VS Code remove the `linkbunny` server entry and its dedicated token input from MCP configuration. Revoking an API token is a separate owner-authorized action in the private Account page.
111
+ For startup failures, check Node.js 22+, npm access, the effective registration and approval state, and token presence in the launching client's environment without printing its value. Allow time for the first npm download. Running `npx` alone waits for protocol input; it does not register a server.
112
+
113
+ For stale guidance, restart the server and read its resource again. Compare it with a fresh fetch of the live canonical skill and the exact local skill loaded by the agent. Check duplicate registrations and the package pin before changing caches. Clearing npm's cache does not upgrade a pinned release. Review and refresh a stale local skill through its installer, then reload the agent session; do not merge conflicting copies into a new workflow contract.
114
+
115
+ Choose a reviewed release and replace the exact version in your client's arguments, then restart the server. Do not replace the pin with `latest` or an unversioned command. For Codex use `codex mcp remove linkbunny`; for Claude Code use `claude mcp remove linkbunny`; for VS Code remove the `linkbunny` server entry from MCP configuration. Revoking an API token is a separate owner-authorized action in the private Account page.
43
116
 
44
117
  ## Development
45
118
 
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ var API_BASE_URL = "https://api.getlinkbunny.com/v1";
16
16
  var REQUEST_TIMEOUT_MS = 1e4;
17
17
  var MAX_RESPONSE_BYTES = 1048576;
18
18
  var SKILL_URI = "linkbunny://guidance/skill";
19
- var VERSION = "0.1.0";
19
+ var VERSION = "0.1.1";
20
20
 
21
21
  // src/privacy.ts
22
22
  function redact({
@@ -403,9 +403,71 @@ function nextAction({
403
403
 
404
404
  // src/env.ts
405
405
  import { z as z5 } from "zod";
406
+ import { open } from "node:fs/promises";
407
+ import { constants } from "node:fs";
408
+ import { isAbsolute } from "node:path";
406
409
  var environmentSchema = z5.object({
407
410
  LINKBUNNY_API_TOKEN: z5.string().min(32).max(512).regex(/^[A-Za-z0-9._~-]+$/)
408
411
  });
412
+ function parseTokenFile({ text }) {
413
+ const lines = text.split(/\r?\n/);
414
+ for (const line of lines) {
415
+ if (/^\s*(?:#.*)?$/.test(line)) {
416
+ continue;
417
+ }
418
+ if (!/^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=\s*(?:"[^"\r\n]*"|'[^'\r\n]*'|[^'"`\r\n]*?)\s*(?:#.*)?$/.test(
419
+ line
420
+ )) {
421
+ throw new Error("Invalid single-line token file.");
422
+ }
423
+ }
424
+ const entries = lines.filter((line) => {
425
+ return /^\s*(?:export\s+)?LINKBUNNY_API_TOKEN\b/.test(line);
426
+ });
427
+ const match = entries.length === 1 ? /^\s*(?:export\s+)?LINKBUNNY_API_TOKEN\s*=\s*(?:"([A-Za-z0-9._~-]+)"|'([A-Za-z0-9._~-]+)'|([A-Za-z0-9._~-]+))\s*(?:#.*)?$/.exec(
428
+ entries[0]
429
+ ) : null;
430
+ return readEnvironment({
431
+ LINKBUNNY_API_TOKEN: match?.[1] ?? match?.[2] ?? match?.[3]
432
+ });
433
+ }
434
+ async function readStartupEnvironment({
435
+ args
436
+ }) {
437
+ if (args.length === 0) {
438
+ return readEnvironment();
439
+ }
440
+ if (args.length !== 2 || args[0] !== "--token-file" || !isAbsolute(args[1])) {
441
+ throw new Error("Use --token-file with one absolute secret-file path.");
442
+ }
443
+ const file = await open(args[1], constants.O_RDONLY | constants.O_NONBLOCK);
444
+ try {
445
+ const stat = await file.stat();
446
+ if (!stat.isFile() || stat.size > 65536) {
447
+ throw new Error("Invalid token file.");
448
+ }
449
+ const buffer = Buffer.alloc(65537);
450
+ let total = 0;
451
+ while (total < buffer.length) {
452
+ const { bytesRead } = await file.read(
453
+ buffer,
454
+ total,
455
+ buffer.length - total,
456
+ total
457
+ );
458
+ if (bytesRead === 0) {
459
+ break;
460
+ }
461
+ total += bytesRead;
462
+ }
463
+ if (total > 65536) {
464
+ throw new Error("Invalid token file.");
465
+ }
466
+ return parseTokenFile({ text: buffer.subarray(0, total).toString("utf8") });
467
+ } finally {
468
+ await file.close();
469
+ }
470
+ }
409
471
  function readEnvironment(input = process.env) {
410
472
  const result = environmentSchema.safeParse(input);
411
473
  if (!result.success) {
@@ -418,7 +480,7 @@ function readEnvironment(input = process.env) {
418
480
 
419
481
  // src/index.ts
420
482
  async function main() {
421
- const env = readEnvironment();
483
+ const env = await readStartupEnvironment({ args: process.argv.slice(2) });
422
484
  const transport = new StdioServerTransport();
423
485
  const send = transport.send.bind(transport);
424
486
  transport.send = async (message) => send(
package/dist/skill.md CHANGED
@@ -106,9 +106,12 @@ this value. After receiving an assignment, tell the owner in plain English: "I w
106
106
  source inventory prefix for a page linking to `targetURL` with `requestedAnchor`." This describes
107
107
  the next investigation only; obtain separate owner authorization before publishing anything.
108
108
 
109
- Candidates may differ by at most 28 Domain Rating points, inclusive. Distinct profiles on distinct
110
- registrable domains may match even when the same account owns them; balances remain isolated per
111
- profile. A profile is never eligible to target itself. LinkBunny does not assign the reverse of a
109
+ LinkBunny matches candidates within a bounded Domain Rating gap, including the limit. The marketplace
110
+ operator may periodically adjust this range as marketplace supply changes. Being within range does
111
+ not guarantee an assignment.
112
+
113
+ Distinct profiles on distinct registrable domains may match even when the same account owns them;
114
+ balances remain isolated per profile. A profile is never eligible to target itself. LinkBunny does not assign the reverse of a
112
115
  pair while the first direction is pending or after it is reported. Expiry or rejection releases
113
116
  reverse-direction eligibility.
114
117
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@linkbunny/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@linkbunny/mcp",
9
- "version": "0.1.0",
9
+ "version": "0.1.1",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@modelcontextprotocol/server": "2.0.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkbunny/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Local stdio MCP tools for owner-authorized LinkBunny workflows",
5
5
  "type": "module",
6
6
  "bin": {