Metadata-Version: 2.4
Name: bowmark-mcp
Version: 2.1.0
Summary: Bowmark MCP over stdio — a thin bridge to the hosted Bowmark MCP at https://api.bowmark.ai/mcp. The web as callable functions.
Project-URL: Homepage, https://bowmark.ai
Project-URL: Documentation, https://bowmark.ai/install
Project-URL: Repository, https://github.com/bowmark-ai/mcp
Author-email: Bowmark AI <christopher@bowmark.ai>
License: MIT
Keywords: ai-agents,bowmark,browser-automation,mcp,model-context-protocol
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: mcp<2,>=1.16
Description-Content-Type: text/markdown

# 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

```sh
uvx bowmark-mcp        # or: pipx run bowmark-mcp / python -m bowmark_mcp
```

Example — browser-use:

```python
from browser_use.mcp.client import MCPClient

bowmark = MCPClient(server_name="bowmark", command="uvx", args=["bowmark-mcp"])
```

Example — any MCP host config:

```json
{ "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_client` is
  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 a `run` that drives real browsers.
- **The real host's name is relayed upstream.** The api tailors its operating
  guidance per host and detects the host from `clientInfo` on the handshake —
  but through a bridge that names the BRIDGE, so every install here would read
  the platform-neutral text. So the host's own `clientInfo.name` (from OUR stdio
  handshake) is forwarded as `X-Bowmark-Client` on 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 `/pypi` destination** is registered in
  `apps/api/src/mcp-destinations.ts` + `mcp-registry/sources.json` (PyPI stdio
  bridge channel). It carries the channel and deliberately pins NO platform,
  because the relayed `X-Bowmark-Client` above is a better answer than any pin.
  The old `?s=p` query form still resolves for installs already in the wild.
  Adding a destination: `.claude/rules/mcp-destinations.md`.
- **The `mcp-name: ai.bowmark/bowmark` line 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.toml` when it changes. Not wired into release-please.

## Tests

```sh
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:

```sh
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`](../.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`
