bowmark-mcp 0.1.0
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 +79 -0
- package/dist/bridge.js +73 -0
- package/dist/cli.js +6 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# bowmark-mcp — Bowmark MCP over stdio (Node)
|
|
2
|
+
|
|
3
|
+
Bowmark gives agents pre-computed navigation recipes for public websites (skip
|
|
4
|
+
explore-and-discover). The canonical server is hosted, streamable HTTP, no auth
|
|
5
|
+
required: `https://api.bowmark.ai/mcp`.
|
|
6
|
+
|
|
7
|
+
This package is a **thin stdio bridge** to that hosted server, for MCP hosts
|
|
8
|
+
whose client only speaks stdio, in Node-flavored environments. Tool schemas,
|
|
9
|
+
descriptions, and results pass through verbatim — the hosted server stays the
|
|
10
|
+
single source of truth; nothing is reimplemented here. (Python-flavored
|
|
11
|
+
environments: the same bridge exists on PyPI as `bowmark-mcp` — `uvx
|
|
12
|
+
bowmark-mcp`.)
|
|
13
|
+
|
|
14
|
+
`mcp-name: ai.bowmark/bowmark`
|
|
15
|
+
|
|
16
|
+
## Use
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npx bowmark-mcp
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Any MCP host config:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{ "mcpServers": { "bowmark": { "command": "npx", "args": ["bowmark-mcp"] } } }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
If your host speaks streamable HTTP, skip this bridge and connect directly to
|
|
29
|
+
`https://api.bowmark.ai/mcp`.
|
|
30
|
+
|
|
31
|
+
## Environment
|
|
32
|
+
|
|
33
|
+
| Var | Meaning |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `BOWMARK_MCP_URL` | Target MCP URL. Default `https://api.bowmark.ai/mcp?s=n` (`?s=n` attributes the install to the npm bridge). Point at `http://localhost:3001/mcp` for a local Bowmark API. |
|
|
36
|
+
| `BOWMARK_API_KEY` | Optional. Forwarded as `X-Bowmark-Key`; a free key (bowmark.ai dashboard) raises the anonymous per-IP daily synthesis cap to your plan budget. |
|
|
37
|
+
|
|
38
|
+
## Design notes (repo-internal)
|
|
39
|
+
|
|
40
|
+
- **One remote session per request, retried once.** The hosted MCP is
|
|
41
|
+
stateless, so a fresh connection is semantically identical and its cost (one
|
|
42
|
+
initialize round-trip) is noise next to an `ask` synthesis — and it
|
|
43
|
+
sidesteps long-lived-connection failure modes without reconnect bookkeeping.
|
|
44
|
+
Mirrors `packages/bowmark-mcp/python` (the PyPI bridge) exactly.
|
|
45
|
+
- **Pass-through only.** No tool logic lives here; the agent-surfaces sync
|
|
46
|
+
rule in the root CLAUDE.md is unaffected because descriptions/schemas ride
|
|
47
|
+
through from `apps/api/src/routes/mcp.ts`. `callTool` results are returned
|
|
48
|
+
verbatim (content, structuredContent, isError).
|
|
49
|
+
- **`?s=n` source code** is registered in `apps/api/src/mcp-sources.ts` +
|
|
50
|
+
`mcp-registry/sources.json` (npm stdio bridge channel; the PyPI bridge is
|
|
51
|
+
`?s=p`).
|
|
52
|
+
- **The `mcp-name: ai.bowmark/bowmark` line above is load-bearing**: the
|
|
53
|
+
official MCP Registry validates npm package ownership by finding that
|
|
54
|
+
marker in the package README. Don't remove it.
|
|
55
|
+
- **Versioning is manual** (thin bridge, not the api): bump `package.json`
|
|
56
|
+
when it changes. Not wired into release-please; `private` is deliberately
|
|
57
|
+
absent so npm publish works — release-please doesn't manage this package.
|
|
58
|
+
|
|
59
|
+
## Tests
|
|
60
|
+
|
|
61
|
+
Network-free unit tests live in the monorepo suite:
|
|
62
|
+
`tests/unit/mcp-stdio-node.test.ts` (`pnpm test:unit`). The remote hop is
|
|
63
|
+
injected at the `callRemote`/`buildServer` seams.
|
|
64
|
+
|
|
65
|
+
## Publishing to npm
|
|
66
|
+
|
|
67
|
+
To ship a version: **bump `version` in `package.json` (and the two
|
|
68
|
+
`version: "0.1.0"` literals in `src/bridge.ts`) and merge** —
|
|
69
|
+
`.github/workflows/publish-bowmark-mcp.yml` compares the manifest against
|
|
70
|
+
live npm on every merge touching this folder and publishes only when the
|
|
71
|
+
version is new (requires the `NPM_TOKEN_BOWMARK_MCP` Actions secret; token
|
|
72
|
+
lives in the 1Password item "npm bowmark-mcp publish token"). Not
|
|
73
|
+
release-please; the bump IS the release action.
|
|
74
|
+
|
|
75
|
+
After the FIRST publish: add an npm `packages` entry to
|
|
76
|
+
`mcp-registry/server.json` (same after-publish ordering rule as PyPI — the
|
|
77
|
+
registry validates the `mcp-name` marker on the live npm README), and swap
|
|
78
|
+
the website stdio tab's Node step from `npx mcp-remote` to
|
|
79
|
+
`npx bowmark-mcp`.
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
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?s=n
|
|
12
|
+
// (`?s=n` tags the install source as the npm bridge;
|
|
13
|
+
// 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?s=n";
|
|
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
|
+
async function withRemote(fn) {
|
|
38
|
+
const client = new Client({ name: "bowmark-mcp-bridge", version: "0.1.0" });
|
|
39
|
+
const headers = authHeaders();
|
|
40
|
+
const transport = new StreamableHTTPClientTransport(new URL(targetUrl()), {
|
|
41
|
+
requestInit: headers ? { headers } : undefined,
|
|
42
|
+
});
|
|
43
|
+
await client.connect(transport);
|
|
44
|
+
try {
|
|
45
|
+
return await fn(client);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
await client.close().catch(() => { });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** One retry on any failure: the remote is stateless, so a fresh session is
|
|
52
|
+
* equivalent, and a transient network blip shouldn't fail the host's call. */
|
|
53
|
+
export async function callRemote(fn, connect = withRemote) {
|
|
54
|
+
try {
|
|
55
|
+
return await connect(fn);
|
|
56
|
+
}
|
|
57
|
+
catch (first) {
|
|
58
|
+
console.error(`bowmark-mcp: retrying after: ${String(first)}`);
|
|
59
|
+
return await connect(fn);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function buildServer(remote = callRemote) {
|
|
63
|
+
const server = new Server({ name: "bowmark", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
64
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
65
|
+
return await remote((c) => c.listTools());
|
|
66
|
+
});
|
|
67
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
68
|
+
// Returned verbatim — content, structuredContent, and isError all ride
|
|
69
|
+
// through, so upstream errors reach the host exactly as the api sent them.
|
|
70
|
+
return await remote((c) => c.callTool({ name: req.params.name, arguments: req.params.arguments ?? {} }));
|
|
71
|
+
});
|
|
72
|
+
return server;
|
|
73
|
+
}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
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 } from "./bridge.js";
|
|
5
|
+
const server = buildServer();
|
|
6
|
+
await server.connect(new StdioServerTransport());
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bowmark-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bowmark MCP over stdio \u2014 a thin bridge to the hosted Bowmark MCP at https://api.bowmark.ai/mcp",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Bowmark AI <christopher@bowmark.ai>",
|
|
7
|
+
"homepage": "https://bowmark.ai",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/bowmark-ai/bowmark-mcp.git",
|
|
11
|
+
"directory": "node"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"bowmark",
|
|
17
|
+
"browser-automation",
|
|
18
|
+
"ai-agents"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"bin": {
|
|
22
|
+
"bowmark-mcp": "dist/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json",
|
|
33
|
+
"prepublishOnly": "rm -rf dist && tsc -p tsconfig.json",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.10.7",
|
|
41
|
+
"typescript": "~5.9.2"
|
|
42
|
+
}
|
|
43
|
+
}
|