diff --git a/.claude/rules/session-protocol.md b/.claude/rules/session-protocol.md index f12152f..f17a011 100644 --- a/.claude/rules/session-protocol.md +++ b/.claude/rules/session-protocol.md @@ -4,6 +4,14 @@ State Hub: http://127.0.0.1:8000 ### Session Protocol +**Step 0 — Tunnel health** + +Before anything else: +```bash +bridge status +``` +Bring up any stopped or stale tunnels before accessing remote services. + **Step 1 — Orient** Read the offline-safe brief first: diff --git a/Makefile b/Makefile index bc36205..6861a59 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: test lint install +.PHONY: test lint install mcp-http mcp-stop test: uv run pytest @@ -8,3 +8,9 @@ lint: install: uv tool install -e . + +mcp-http: ## Start MCP server in SSE mode (default port 8002) + BRIDGE_MCP_PORT=$${BRIDGE_MCP_PORT:-8002} uv run python src/bridge/mcp_server/server.py --http + +mcp-stop: ## Stop MCP server running on port 8002 + @lsof -ti:$${BRIDGE_MCP_PORT:-8002} | xargs -r kill -TERM && echo "MCP server stopped" || echo "No MCP server running on port $${BRIDGE_MCP_PORT:-8002}" diff --git a/pyproject.toml b/pyproject.toml index 0ac7c95..d722d67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ dependencies = [ "typer>=0.12", "pyyaml>=6.0", "httpx>=0.27", - "fastmcp>=2.0.0", + "fastmcp>=2.0.0,<3.1.0", ] [project.scripts] diff --git a/src/bridge/mcp_server/server.py b/src/bridge/mcp_server/server.py index 23dd4ef..0780488 100644 --- a/src/bridge/mcp_server/server.py +++ b/src/bridge/mcp_server/server.py @@ -513,4 +513,13 @@ def resource_catalog_targets() -> str: # --------------------------------------------------------------------------- if __name__ == "__main__": - mcp.run(transport="stdio") + import argparse + parser = argparse.ArgumentParser(description="OpsBridge MCP server") + parser.add_argument("--http", action="store_true", help="Run in SSE/HTTP mode instead of stdio") + args = parser.parse_args() + + if args.http: + port = int(os.environ.get("BRIDGE_MCP_PORT", "8002")) + mcp.run(transport="sse", host="127.0.0.1", port=port) + else: + mcp.run(transport="stdio") diff --git a/uv.lock b/uv.lock index c5c6d93..ee79984 100644 --- a/uv.lock +++ b/uv.lock @@ -345,7 +345,7 @@ wheels = [ [[package]] name = "fastmcp" -version = "3.1.0" +version = "3.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "authlib" }, @@ -365,14 +365,13 @@ dependencies = [ { name = "python-dotenv" }, { name = "pyyaml" }, { name = "rich" }, - { name = "uncalled-for" }, { name = "uvicorn" }, { name = "watchfiles" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/70/862026c4589441f86ad3108f05bfb2f781c6b322ad60a982f40b303b47d7/fastmcp-3.1.0.tar.gz", hash = "sha256:e25264794c734b9977502a51466961eeecff92a0c2f3b49c40c070993628d6d0", size = 17347083 } +sdist = { url = "https://files.pythonhosted.org/packages/11/6b/1a7ec89727797fb07ec0928e9070fa2f45e7b35718e1fe01633a34c35e45/fastmcp-3.0.2.tar.gz", hash = "sha256:6bd73b4a3bab773ee6932df5249dcbcd78ed18365ed0aeeb97bb42702a7198d7", size = 17239351 } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/07/516f5b20d88932e5a466c2216b628e5358a71b3a9f522215607c3281de05/fastmcp-3.1.0-py3-none-any.whl", hash = "sha256:b1f73b56fd3b0cb2bd9e2a144fc650d5cc31587ed129d996db7710e464ae8010", size = 633749 }, + { url = "https://files.pythonhosted.org/packages/0a/5a/f410a9015cfde71adf646dab4ef2feae49f92f34f6050fcfb265eb126b30/fastmcp-3.0.2-py3-none-any.whl", hash = "sha256:f513d80d4b30b54749fe8950116b1aab843f3c293f5cb971fc8665cb48dbb028", size = 606268 }, ] [[package]] @@ -664,7 +663,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "fastmcp", specifier = ">=2.0.0" }, + { name = "fastmcp", specifier = ">=2.0.0,<3.1.0" }, { name = "httpx", specifier = ">=0.27" }, { name = "pyyaml", specifier = ">=6.0" }, { name = "typer", specifier = ">=0.12" }, @@ -1297,15 +1296,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611 }, ] -[[package]] -name = "uncalled-for" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/7c/b5b7d8136f872e3f13b0584e576886de0489d7213a12de6bebf29ff6ebfc/uncalled_for-0.2.0.tar.gz", hash = "sha256:b4f8fdbcec328c5a113807d653e041c5094473dd4afa7c34599ace69ccb7e69f", size = 49488 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/7f/4320d9ce3be404e6310b915c3629fe27bf1e2f438a1a7a3cb0396e32e9a9/uncalled_for-0.2.0-py3-none-any.whl", hash = "sha256:2c0bd338faff5f930918f79e7eb9ff48290df2cb05fcc0b40a7f334e55d4d85f", size = 11351 }, -] - [[package]] name = "uvicorn" version = "0.41.0" diff --git a/workplans/OPS-WP-0002-agent-usability.md b/workplans/OPS-WP-0002-agent-usability.md index 755deb1..110efaf 100644 --- a/workplans/OPS-WP-0002-agent-usability.md +++ b/workplans/OPS-WP-0002-agent-usability.md @@ -4,11 +4,11 @@ type: workplan title: "Agent Usability — MCP Registration, Skill, and Worker Orientation" domain: custodian repo: ops-bridge -status: active +status: done owner: custodian topic_slug: custodian created: "2026-03-21" -updated: "2026-03-21" +updated: "2026-03-26" depends_on: OPS-WP-0001 state_hub_workstream_id: "c195cc40-8be7-462e-be26-a7d6bda34cd5" --- @@ -74,7 +74,7 @@ worker agents: ```task id: OPS-WP-0002-T01 -status: todo +status: done priority: high state_hub_task_id: "27fc6fa1-6d0e-438a-b4a3-c6091931da88" ``` @@ -101,7 +101,7 @@ Gate: `bridge_status()` tool callable via SSE on localhost:8002 after ```task id: OPS-WP-0002-T02 -status: todo +status: done priority: high state_hub_task_id: "2216457d-035e-4804-b685-18975f3c6d1f" ``` @@ -133,7 +133,7 @@ mcp-http`. ```task id: OPS-WP-0002-T03 -status: todo +status: done priority: medium state_hub_task_id: "4b2e39eb-4585-4e60-ab16-9e7909eced74" ``` @@ -178,7 +178,7 @@ identifies and recovers a manually-stopped tunnel. ```task id: OPS-WP-0002-T04 -status: todo +status: done priority: medium state_hub_task_id: "cc64bb07-ea5d-498a-8c14-bb653581efe7" ``` @@ -213,9 +213,9 @@ session protocol references bridge status check. ## Done Criteria -- [ ] `make mcp-http` starts the MCP server on port 8002 (SSE) -- [ ] `bridge_status` and `bridge_check` callable as MCP tools from Claude Code -- [ ] `ops-bridge` registered in `~/.claude.json` at user scope -- [ ] `/bridge` skill surfaces tunnel states and recovers a stopped tunnel -- [ ] Global CLAUDE.md has worker agent bridge protocol -- [ ] All existing tests pass after T01 changes (`make test`) +- [x] `make mcp-http` starts the MCP server on port 8002 (SSE) +- [x] `bridge_status` and `bridge_check` callable as MCP tools from Claude Code +- [x] `ops-bridge` registered in `~/.claude.json` at user scope +- [x] `/bridge` skill surfaces tunnel states and recovers a stopped tunnel +- [x] Global CLAUDE.md has worker agent bridge protocol +- [x] All existing tests pass after T01 changes (`make test`)