bowmark-mcp — Bowmark MCP over stdio
Bowmark turns the interaction-gated web into a callable function library.
An agent reads the library (get_library), writes a short JavaScript script
against it, and sends it to run — Bowmark executes it on the live sites in a
sandbox and hands back the result. The canonical server is hosted, streamable
HTTP, no auth required: https://api.bowmark.ai/mcp.
This package is a thin stdio bridge to that hosted server, for MCP hosts
whose client only speaks stdio (for example browser-use's MCPClient). Tool
schemas, descriptions, and results pass through verbatim — the hosted server
stays the single source of truth; nothing is reimplemented here.
(Node-flavored environments: the same bridge exists on npm as bowmark-mcp —
npx @bowmark/mcp, packages/bowmark-mcp/node/ in the monorepo. The npm name is
scoped and this one is not, deliberately: PyPI cannot be scoped and has no rename
mechanism, so bowmark-mcp here is already what PEP 752 blesses.)
mcp-name: ai.bowmark/bowmark
Use
uvx bowmark-mcp # or: pipx run bowmark-mcp / python -m bowmark_mcp
Example — browser-use:
from browser_use.mcp.client import MCPClient
bowmark = MCPClient(server_name="bowmark", command="uvx", args=["bowmark-mcp"])
Example — any MCP host config:
{ "mcpServers": { "bowmark": { "command": "uvx", "args": ["bowmark-mcp"] } } }
If your host speaks streamable HTTP, skip this bridge and connect directly to
https://api.bowmark.ai/mcp.
Environment
| Var | Meaning |
|---|---|
BOWMARK_MCP_URL |
Target MCP URL. Default https://api.bowmark.ai/mcp/pypi (the /pypi destination attributes the install to this bridge). Point at http://localhost:3001/mcp for a local Bowmark API. |
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. |
Design notes (repo-internal)
- One remote session per request, retried once.
streamablehttp_clientis an anyio-scoped context manager; holding one session across handler tasks trips "exit cancel scope in a different task". The hosted MCP is stateless, so a fresh session per request is semantically identical and costs one initialize round-trip — noise next to arunthat drives real browsers. - The real host's name is relayed upstream. The api tailors its operating
guidance per host and detects the host from
clientInfoon the handshake — but through a bridge that names the BRIDGE, so every install here would read the platform-neutral text. So the host's ownclientInfo.name(from OUR stdio handshake) is forwarded asX-Bowmark-Clienton every proxied request, and the api ranks that above its own handshake. Best-effort: an unknown or missing name simply falls back to the neutral text, never an error. - Pass-through only. No tool logic lives here; the agent-surfaces sync rule
in the root CLAUDE.md is unaffected because descriptions/schemas ride through
from
apps/api/src/routes/mcp.ts. - The
/pypidestination is registered inapps/api/src/mcp-destinations.ts+mcp-registry/sources.json(PyPI stdio bridge channel). It carries the channel and deliberately pins NO platform, because the relayedX-Bowmark-Clientabove is a better answer than any pin. The old?s=pquery form still resolves for installs already in the wild. Adding a destination:.claude/rules/mcp-destinations.md. - The
mcp-name: ai.bowmark/bowmarkline above is load-bearing: the official MCP Registry validates PyPI package ownership by finding that marker in the package README. Don't remove it. - Versioning is manual (this is a thin bridge, not the api): bump
pyproject.tomlwhen it changes. Not wired into release-please.
Tests
cd packages/bowmark-mcp/python
uv run --with pytest --with-editable . pytest -q
Network-free (the remote hop is monkeypatched). A live smoke against prod:
uv run --with-editable . python - <<'EOF'
import asyncio, bowmark_mcp
async def main():
tools = await bowmark_mcp.list_tools_impl()
print([t.name for t in tools])
asyncio.run(main())
EOF
Publishing to PyPI
Live since 2026-07-03, published + cold-verified via uvx bowmark-mcp
against prod. To ship a new version: bump version in pyproject.toml
and merge. The actual publish does NOT run in this monorepo's CI — this
repo is private, and PyPI Trusted Publishing (OIDC) validates against
pyproject.toml's Repository URL, which (correctly) points at the public
mirror github.com/bowmark-ai/mcp, not Metroxe/bowmark. So merging here
only lands the version bump; release-bowmark-mcp.yml mirror-syncs it to
bowmark-ai/mcp, and THAT repo's own .github/workflows/publish.yml
(source-controlled at
packages/bowmark-mcp/.github/workflows/publish.yml
in this monorepo, mirrored in like any other file) does the real uv publish. Auth is PyPI Trusted Publishing (OIDC) — no token, configured
on pypi.org (project → Publishing → GitHub publisher for bowmark-ai/mcp /
publish.yml). Publishing from the public mirror also auto-generates
provenance attestations. Not release-please; the bump IS the release
action. If the registry surface changed, also bump
packages[0].version in mcp-registry/server.json (rides the next
api-release republish — the registry validates ownership via the mcp-name
marker above).
Manual fallback (creds: 1Password item "PyPI", vault
Christopher-Macbook-CLI, token in the Christopher_Bowmark_API_Key field;
service-account op read requires the vault in the path):
cd packages/bowmark-mcp/python && rm -rf dist && uv build && UV_PUBLISH_TOKEN="$(op read 'op://Christopher-Macbook-CLI/PyPI/Christopher_Bowmark_API_Key')" uv publish
Metadata
Release files for bowmark-mcp 2.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bowmark_mcp-2.1.0.tar.gz | 63.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bowmark_mcp-2.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 71.0 kB
Release files / bowmark_mcp-2.1.0.tar.gz
| Download URL | bowmark_mcp-2.1.0.tar.gz |
|---|---|
| Size | 63.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ce5fc1575d28b8ba2c4789796a2bca67fdadae27bb184d910fee68f05d7f87c5
|
|
BLAKE2b-256 checksum How to use checksums |
bcbec3baa6e511d48f4e7da0f78d29c51ba4ddc95c6e964d25ad22319e1a5d55
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.2 {"installer":{"name":"uv","version":"0.12.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / bowmark_mcp-2.1.0-py3-none-any.whl
| Download URL | bowmark_mcp-2.1.0-py3-none-any.whl |
|---|---|
| Size | 7.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
297fa0e5876d6e8efa000d85c14a1ff93df5ae464e53f8f528aed57c329b9cb0
|
|
BLAKE2b-256 checksum How to use checksums |
836d7151ad82fed345f812502dae398ae3a1abeb2a7885353f10605b83422228
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.2 {"installer":{"name":"uv","version":"0.12.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|