bowmark-mcp 2.0.1 → 2.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
@@ -1,108 +1,41 @@
1
- # bowmark-mcp — Bowmark MCP over stdio (Node)
1
+ # bowmark-mcp — compatibility forwarder
2
2
 
3
- Bowmark turns the interaction-gated web into a **callable function library**.
4
- An agent reads the library (`get_library`), writes a short JavaScript script
5
- against it, and sends it to `run` — Bowmark executes it on the live sites in a
6
- sandbox and hands back the result. The canonical server is hosted, streamable
7
- HTTP, no auth required: `https://api.bowmark.ai/mcp`.
3
+ **The bridge moved to [`@bowmark/mcp`](https://www.npmjs.com/package/@bowmark/mcp).**
4
+ This package is the old name, kept working.
8
5
 
9
- This package is a **thin stdio bridge** to that hosted server, for MCP hosts
10
- whose client only speaks stdio, in Node-flavored environments. Tool schemas,
11
- descriptions, and results pass through verbatim — the hosted server stays the
12
- single source of truth; nothing is reimplemented here. (Python-flavored
13
- environments: the same bridge exists on PyPI as `bowmark-mcp` — `uvx
14
- bowmark-mcp`.)
15
-
16
- `mcp-name: ai.bowmark/bowmark`
17
-
18
- ## Use
19
-
20
- ```sh
21
- npx bowmark-mcp
22
- ```
23
-
24
- Any MCP host config:
25
-
26
- ```json
6
+ ```jsonc
7
+ // what you have: still works, prints one notice on stderr
27
8
  { "mcpServers": { "bowmark": { "command": "npx", "args": ["bowmark-mcp"] } } }
28
- ```
29
-
30
- If your host speaks streamable HTTP, skip this bridge and connect directly to
31
- `https://api.bowmark.ai/mcp`.
32
9
 
33
- ## Environment
34
-
35
- | Var | Meaning |
36
- |---|---|
37
- | `BOWMARK_MCP_URL` | Target MCP URL. Default `https://api.bowmark.ai/mcp/npm` (the `/npm` destination attributes the install to this bridge). Point at `http://localhost:3001/mcp` for a local Bowmark API. |
38
- | `BOWMARK_API_KEY` | Optional. Forwarded as `X-Bowmark-Key`; a free key (bowmark.ai dashboard) lifts the anonymous per-IP daily cap to your plan budget. |
10
+ // what to move to
11
+ { "mcpServers": { "bowmark": { "command": "npx", "args": ["@bowmark/mcp"] } } }
12
+ ```
39
13
 
40
- ## Design notes (repo-internal)
14
+ Nothing else changes: same bridge, same tools, same endpoint, same version
15
+ number. `bin/bowmark-mcp.mjs` imports `@bowmark/mcp/cli` in-process and that is
16
+ the whole package. In-process rather than spawned, because a child would need
17
+ stdio, signal and exit-code plumbing to be transparent, and every one of those
18
+ is a way for a stdio transport to break.
41
19
 
42
- - **One remote session per request, retried once.** The hosted MCP is
43
- stateless, so a fresh connection is semantically identical and its cost (one
44
- initialize round-trip) is noise next to a `run` that drives real browsers — and it
45
- sidesteps long-lived-connection failure modes without reconnect bookkeeping.
46
- Mirrors `packages/bowmark-mcp/python` (the PyPI bridge) exactly.
47
- - **The real host's name is relayed upstream.** The api tailors its operating
48
- guidance per host and detects the host from `clientInfo` on the handshake —
49
- but through a bridge that names the BRIDGE, so every install here would read
50
- the platform-neutral text. So the host's own `clientInfo.name` (from OUR stdio
51
- handshake) is forwarded as `X-Bowmark-Client` on every proxied request, and the
52
- api ranks that above its own handshake. Best-effort: an unknown or missing name
53
- simply falls back to the neutral text, never an error.
54
- - **Pass-through only.** No tool logic lives here; the agent-surfaces sync
55
- rule in the root CLAUDE.md is unaffected because descriptions/schemas ride
56
- through from `apps/api/src/routes/mcp.ts`. `callTool` results are returned
57
- verbatim (content, structuredContent, isError).
58
- - **The `/npm` destination** is registered in
59
- `apps/api/src/mcp-destinations.ts` + `mcp-registry/sources.json` (npm stdio
60
- bridge channel; the PyPI bridge is `/pypi`). It carries the channel and
61
- deliberately pins NO platform, because the relayed `X-Bowmark-Client` above is
62
- a better answer than any pin. The old `?s=n` query form still resolves for
63
- installs already in the wild. Adding a destination:
64
- `.claude/rules/mcp-destinations.md`.
65
- - **npm ownership verification needs BOTH markers — the registry checks the
66
- published package.json, not just the README.** The `mcp-name:
67
- ai.bowmark/bowmark` line above and the `"mcpName": "ai.bowmark/bowmark"`
68
- field in `package.json` are both load-bearing; don't remove either. Learned
69
- the hard way 2026-07-04: `release-mcp.yml`'s registry republish failed for
70
- five straight api releases (`server returned status 400: ... "NPM package
71
- 'bowmark-mcp' is missing required 'mcpName' field"`) because only the
72
- README marker was present — the package.json field is what `mcp-publisher
73
- publish` actually validates against the live npm package.
74
- - **Versioning is manual** (thin bridge, not the api): bump `package.json`
75
- when it changes. Not wired into release-please; `private` is deliberately
76
- absent so npm publish works — release-please doesn't manage this package.
20
+ **PyPI did not move.** `uvx bowmark-mcp` is unchanged, and it is now the
21
+ *blessed* name there: PEP 752 standardises hyphen-prefixed namespaces rather
22
+ than `@scope`, and PyPI has no rename, alias or redirect mechanism at all. Only
23
+ npm is scoped.
77
24
 
78
- ## Tests
25
+ ## Why a forwarder rather than `npm deprecate`
79
26
 
80
- Network-free unit tests live in the monorepo suite:
81
- `tests/unit/mcp-stdio-node.test.ts` (`pnpm test:unit`). The remote hop is
82
- injected at the `callRemote`/`buildServer` seams.
27
+ `npm deprecate` provides a warning to anyone who installs the package. It warns,
28
+ it never blocks, and npm has no "moved" mechanism at all. The name has real
29
+ traffic (3,032 npm installs in the month to 2026-07-28) and every published
30
+ config, registry listing and benchmark harness names it, so the old name has to
31
+ keep *working*, not merely warn. `npm deprecate bowmark-mcp` is run as well: it
32
+ is the notice, this package is the compatibility.
83
33
 
84
- ## Publishing to npm
34
+ It is never deleted and never unpublished.
85
35
 
86
- **Live since 2026-07-04** (published by CI + cold-verified via
87
- `npx -y bowmark-mcp` against prod). To ship a version: **bump `version` in
88
- `package.json` (and the two `version` literals in `src/bridge.ts`) and
89
- merge.** The actual publish does NOT run in this monorepo's CI — this repo
90
- is private, and npm Trusted Publishing (OIDC) validates against
91
- `package.json`'s `repository.url`, which (correctly) points at the public
92
- mirror `github.com/bowmark-ai/mcp`, not `Metroxe/bowmark`. So merging here
93
- only lands the version bump; `release-bowmark-mcp.yml` mirror-syncs it to
94
- `bowmark-ai/mcp`, and THAT repo's own `.github/workflows/publish.yml`
95
- (source-controlled at
96
- [`packages/bowmark-mcp/.github/workflows/publish.yml`](../.github/workflows/publish.yml)
97
- in this monorepo, mirrored in like any other file) does the real `npm
98
- publish`. Auth is npm **Trusted Publishing** (OIDC) — no token, configured
99
- on npmjs.com (package Settings → Trusted Publisher → GitHub Actions, repo
100
- `bowmark-ai/mcp`, workflow `publish.yml`). Publishing from the public
101
- mirror also auto-generates provenance attestations, which npm only produces
102
- for public-repo publishes — impossible from this private monorepo
103
- regardless of `repository.url`. Not release-please; the bump IS the release
104
- action.
36
+ ## Versioning
105
37
 
106
- `mcp-registry/server.json` carries the matching npm `packages` entry (landed
107
- after the first publish per the mcp-name ordering rule), and the website
108
- stdio tab's Node step points at `npx bowmark-mcp`.
38
+ The forwarder's `version` and its `@bowmark/mcp` dependency are pinned to the
39
+ canonical package's version, exactly. A forwarder on an older version would
40
+ silently serve an older bridge to everyone who never migrated, which is the one
41
+ thing it exists to prevent. Both pins are checked in CI upstream.
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ // The `bowmark-mcp` npm name, kept alive forever.
3
+ //
4
+ // The bridge itself moved to `@bowmark/mcp` on 2026-08-05. This package is the
5
+ // old name continuing to work: npm cannot be unpublished past 72h, `npm
6
+ // deprecate` only WARNS and never blocks an install, and there is no "moved"
7
+ // mechanism on npm at all — so a forwarder is the only thing that actually
8
+ // preserves compatibility for the configs already in the wild.
9
+ // `.claude/rules/agent-surfaces.md` § Published packages cannot be unpublished.
10
+ //
11
+ // Two properties this file exists to hold:
12
+ //
13
+ // - **stdout belongs to the protocol.** The notice goes to stderr. A single
14
+ // stray byte on stdout desynchronises the JSON-RPC framing and the host
15
+ // drops the server with no useful error.
16
+ // - **The real CLI is imported IN-PROCESS, never spawned.** A child process
17
+ // would need stdio, signal and exit-code plumbing to be transparent, and
18
+ // every one of those is a way for a stdio transport to break. An import
19
+ // shares this process's own stdio by construction.
20
+ //
21
+ // `@bowmark/mcp` declares `./cli` in its `exports` map precisely so this line
22
+ // is a supported entry point rather than a deep import into someone's dist/.
23
+ // No em dash in this string: it is outbound copy, read by everyone who installs
24
+ // the old name. Root CLAUDE.md, "no em dashes in outbound-to-others content".
25
+ console.error(
26
+ "bowmark-mcp: this package now forwards to @bowmark/mcp. Point your config at `npx @bowmark/mcp` (same bridge, same behaviour).",
27
+ );
28
+
29
+ await import("@bowmark/mcp/cli");
package/package.json CHANGED
@@ -1,44 +1,32 @@
1
1
  {
2
2
  "name": "bowmark-mcp",
3
- "version": "2.0.1",
4
- "mcpName": "ai.bowmark/bowmark",
5
- "description": "Bowmark MCP over stdio \u2014 a thin bridge to the hosted Bowmark MCP at https://api.bowmark.ai/mcp. The web as callable functions: read a typed library, write a script, Bowmark runs it on the live sites.",
6
- "license": "MIT",
3
+ "version": "2.1.1",
4
+ "description": "Compatibility forwarder. The Bowmark stdio MCP bridge now ships as @bowmark/mcp; this package re-execs it so every config already in the wild keeps working.",
5
+ "license": "UNLICENSED",
7
6
  "author": "Bowmark AI <christopher@bowmark.ai>",
8
7
  "homepage": "https://bowmark.ai",
9
8
  "repository": {
10
9
  "type": "git",
11
10
  "url": "git+https://github.com/bowmark-ai/mcp.git",
12
- "directory": "node"
11
+ "directory": "node-compat"
13
12
  },
14
13
  "keywords": [
15
14
  "mcp",
16
15
  "model-context-protocol",
17
- "bowmark",
18
- "browser-automation",
19
- "ai-agents"
16
+ "bowmark"
20
17
  ],
21
18
  "type": "module",
22
19
  "bin": {
23
- "bowmark-mcp": "dist/cli.js"
20
+ "bowmark-mcp": "bin/bowmark-mcp.mjs"
24
21
  },
25
22
  "files": [
26
- "dist",
23
+ "bin",
27
24
  "README.md"
28
25
  ],
29
26
  "engines": {
30
27
  "node": ">=20"
31
28
  },
32
- "scripts": {
33
- "build": "tsc -p tsconfig.json",
34
- "prepublishOnly": "rm -rf dist && tsc -p tsconfig.json",
35
- "typecheck": "tsc -p tsconfig.json --noEmit"
36
- },
37
29
  "dependencies": {
38
- "@modelcontextprotocol/sdk": "^1.29.0"
39
- },
40
- "devDependencies": {
41
- "@types/node": "^22.10.7",
42
- "typescript": "~5.9.2"
30
+ "@bowmark/mcp": "2.1.1"
43
31
  }
44
32
  }
package/dist/bridge.js DELETED
@@ -1,120 +0,0 @@
1
- // Bowmark MCP stdio bridge (Node) — the npm twin of packages/bowmark-mcp/python (PyPI).
2
- //
3
- // A thin stdio MCP server that proxies every request to the hosted Bowmark
4
- // MCP (streamable HTTP, https://api.bowmark.ai/mcp). Exists for stdio-only
5
- // MCP hosts in Node-flavored environments: they run `npx bowmark-mcp` and get
6
- // the exact hosted tools — schemas, descriptions, and envelopes pass through
7
- // verbatim, so the hosted server stays the single source of truth and this
8
- // package never needs a re-publish when the api's tools change.
9
- //
10
- // Env:
11
- // BOWMARK_MCP_URL Target MCP URL. Default https://api.bowmark.ai/mcp/npm
12
- // (the `/npm` destination attributes the install to this
13
- // bridge; point at http://localhost:3001/mcp for a local API).
14
- // BOWMARK_API_KEY Optional key, forwarded as X-Bowmark-Key. Omit for the
15
- // anonymous tier.
16
- //
17
- // Design note — one remote session PER REQUEST, retried once. The hosted MCP
18
- // is stateless and auth-free, so a fresh connection is semantically identical
19
- // to a held one, costs one initialize round-trip (noise next to an `ask`
20
- // synthesis), and sidesteps every long-lived-connection failure mode (idle
21
- // HTTP timeouts, half-open sockets) without reconnect bookkeeping. Mirrors
22
- // the Python bridge's contract exactly.
23
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
24
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
25
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
26
- import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
27
- export const DEFAULT_URL = "https://api.bowmark.ai/mcp/npm";
28
- export function targetUrl(env = process.env) {
29
- return env.BOWMARK_MCP_URL?.trim() || DEFAULT_URL;
30
- }
31
- /** Optional API key -> X-Bowmark-Key header (the hosted MCP also accepts
32
- * Authorization: Bearer; one header is enough). */
33
- export function authHeaders(env = process.env) {
34
- const key = env.BOWMARK_API_KEY?.trim();
35
- return key ? { "X-Bowmark-Key": key } : undefined;
36
- }
37
- /** Relay WHICH HOST is really on the other end.
38
- *
39
- * The hosted MCP tailors its instructions per host (ChatGPT vs a chat app vs a
40
- * coding agent), and it detects the host from the `clientInfo` on the
41
- * handshake. Through this bridge that signal is destroyed: we open our OWN
42
- * session upstream, so the name the api would see is `bowmark-mcp-bridge` and
43
- * every install through npm resolves to the platform-neutral text.
44
- *
45
- * The real host named itself on OUR stdio handshake, so relay that name on
46
- * every proxied request. Best-effort: `undefined` before the host has
47
- * initialized, and the api treats an unrecognized name the same as none. */
48
- export function clientHeaders(hostName) {
49
- const name = hostName?.trim();
50
- return name ? { "X-Bowmark-Client": name } : undefined;
51
- }
52
- async function withRemote(fn, hostName) {
53
- const client = new Client({ name: "bowmark-mcp-bridge", version: "2.0.1" });
54
- const headers = { ...authHeaders(), ...clientHeaders(hostName) };
55
- const transport = new StreamableHTTPClientTransport(new URL(targetUrl()), {
56
- requestInit: Object.keys(headers).length > 0 ? { headers } : undefined,
57
- });
58
- await client.connect(transport);
59
- try {
60
- return await fn(client);
61
- }
62
- finally {
63
- await client.close().catch(() => { });
64
- }
65
- }
66
- /** One retry on any failure: the remote is stateless, so a fresh session is
67
- * equivalent, and a transient network blip shouldn't fail the host's call. */
68
- export async function callRemote(fn, connect = withRemote, hostName) {
69
- try {
70
- return await connect(fn, hostName);
71
- }
72
- catch (first) {
73
- console.error(`bowmark-mcp: retrying after: ${String(first)}`);
74
- return await connect(fn, hostName);
75
- }
76
- }
77
- /** The hosted server's `instructions` string, fetched with one initialize.
78
- *
79
- * Instructions are handed to the host at OUR initialize, which happens before we
80
- * have talked to the remote — so unlike tools they cannot be lazily proxied per
81
- * request. Fetching once at startup keeps the hosted server the single source of
82
- * truth (the alternative, a hardcoded copy here, would drift the moment the api
83
- * changed it and could only be corrected by an npm re-publish).
84
- *
85
- * KNOWN LIMITATION, and it is the reason this is not a bug report: the api tailors
86
- * its instructions PER HOST, but this fetch happens before any host has connected
87
- * to us, so we cannot relay a host name yet and the api answers with its
88
- * platform-neutral text. Tool descriptions, fetched per request once the host HAS
89
- * identified itself, are correctly per-host. Pinning a destination in
90
- * BOWMARK_MCP_URL (e.g. .../mcp/cursor) is the way to get tailored instructions
91
- * through a bridge.
92
- *
93
- * Returns undefined on any failure: instructions are a ranking hint, and a
94
- * bridge that refused to start because a hint was unavailable would be strictly
95
- * worse than one that starts without it. */
96
- export async function fetchInstructions(remote = callRemote) {
97
- try {
98
- return await remote(async (c) => c.getInstructions());
99
- }
100
- catch (err) {
101
- console.error(`bowmark-mcp: could not read server instructions: ${String(err)}`);
102
- return undefined;
103
- }
104
- }
105
- export function buildServer(remote = callRemote, instructions) {
106
- const server = new Server({ name: "bowmark", version: "2.0.1" }, { capabilities: { tools: {} }, instructions });
107
- // Whoever ran us said who they are on the stdio handshake. Read it per
108
- // request rather than once: `tools/list` can arrive before the SDK has
109
- // recorded it, and a fresh read costs nothing.
110
- const hostName = () => server.getClientVersion()?.name;
111
- server.setRequestHandler(ListToolsRequestSchema, async () => {
112
- return await remote((c) => c.listTools(), undefined, hostName());
113
- });
114
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
115
- // Returned verbatim — content, structuredContent, and isError all ride
116
- // through, so upstream errors reach the host exactly as the api sent them.
117
- return await remote((c) => c.callTool({ name: req.params.name, arguments: req.params.arguments ?? {} }), undefined, hostName());
118
- });
119
- return server;
120
- }
package/dist/cli.js DELETED
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env node
2
- // stdout belongs to the protocol; anything human goes to stderr.
3
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { buildServer, callRemote, fetchInstructions } from "./bridge.js";
5
- // One initialize before we serve, purely to carry the hosted server's
6
- // `instructions` through to the host — they ride on OUR initialize, so they
7
- // cannot be proxied per request the way tools are. Never fatal; see
8
- // fetchInstructions.
9
- const server = buildServer(callRemote, await fetchInstructions());
10
- await server.connect(new StdioServerTransport());