From 7fb51e9918d25bacf31f9e50f2f07a3e7075bc1a Mon Sep 17 00:00:00 2001 From: Andrey Oblivantsev Date: Thu, 13 Aug 2026 21:40:40 +0100 Subject: [PATCH] feat: PicoClaw compose profile exposes brain MCP on localhost (#23) Agent is not shipped. docker compose --profile picoclaw up brain-mcp and point the client at 127.0.0.1:8630/mcp. --- PLAN.md | 2 +- README.md | 1 + bin/tools/test_published_docs.py | 12 ++++++++++++ compose.yaml | 19 +++++++++++++++++++ deploy/picoclaw/mcp.json.example | 8 ++++++++ docs/picoclaw.md | 18 ++++++++++++++++++ skills/picoclaw/SKILL.md | 3 ++- 7 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 deploy/picoclaw/mcp.json.example create mode 100644 docs/picoclaw.md diff --git a/PLAN.md b/PLAN.md index 513d056..5213111 100644 --- a/PLAN.md +++ b/PLAN.md @@ -59,7 +59,7 @@ detective method: **a fact needs ≥2 independent sources or it is brain/get.go stats.go eval.go # Go read (cgo); Python bin/kb/* CI fallback brain/watch.go brain/search.go deduction: facts → info → web-search - brain/serve.go HTTP API in-process + OpenAPI/MCP (D20) + brain/serve.go HTTP API in-process + OpenAPI/MCP (D20); compose profile picoclaw mail/import.go JSON → markdown (no brain write) markdown/import.go mistune leaves postgres/query.go read-only YAML (wraps bin/db/psql-yq) diff --git a/README.md b/README.md index 34c50fb..21c6de7 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,7 @@ Docker (optional, cached model + var volumes): docker compose run --rm brain index # (re)index corpus docker compose run --rm brain search "query" # one-shot query docker compose run --rm brain serve # bin/brain/serve.go +docker compose --profile picoclaw up brain-mcp # MCP on 127.0.0.1:8630 docker compose up brain-watch # auto re-index on change ``` diff --git a/bin/tools/test_published_docs.py b/bin/tools/test_published_docs.py index b892fbc..d5510d0 100644 --- a/bin/tools/test_published_docs.py +++ b/bin/tools/test_published_docs.py @@ -59,6 +59,18 @@ class PublishedDocsTest(unittest.TestCase): self.assertNotIn("password", settings.lower()) self.assertIn("json", settings) + def test_picoclaw_compose_profile_has_mcp_example(self) -> None: + compose = (ROOT / "compose.yaml").read_text() + self.assertIn('profiles: ["picoclaw"]', compose) + self.assertIn("127.0.0.1:8630", compose) + example = (ROOT / "deploy" / "picoclaw" / "mcp.json.example").read_text() + self.assertIn("127.0.0.1:8630/mcp", example) + self.assertNotIn("password", example.lower()) + self.assertNotIn("token", example.lower()) + docs = (ROOT / "docs" / "picoclaw.md").read_text() + self.assertIn("search", docs) + self.assertIn("throttled", docs) + def test_readme_read_path_is_go(self) -> None: plan = (ROOT / "PLAN.md").read_text() self.assertIn("get.go", plan) diff --git a/compose.yaml b/compose.yaml index f9460ed..90cce69 100644 --- a/compose.yaml +++ b/compose.yaml @@ -76,6 +76,25 @@ services: - ./deploy/searxng/limiter.toml:/etc/searxng/limiter.toml:ro restart: unless-stopped + # MCP endpoint for an external agent (PicoClaw is not shipped here). + # docker compose --profile picoclaw up brain-mcp + # Point the agent at http://127.0.0.1:8630/mcp (see deploy/picoclaw/). + brain-mcp: + profiles: ["picoclaw"] + image: ghcr.io/eslider/2dph:latest + environment: *env + volumes: + - kb-model:/data/hf + - kb-var:/data + - ~/.config/brain:/secret:ro + command: ["brain", "serve"] + ports: + - "127.0.0.1:8630:8630" + read_only: true + tmpfs: + - /tmp + restart: unless-stopped + volumes: kb-model: kb-var: \ No newline at end of file diff --git a/deploy/picoclaw/mcp.json.example b/deploy/picoclaw/mcp.json.example new file mode 100644 index 0000000..5a04663 --- /dev/null +++ b/deploy/picoclaw/mcp.json.example @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "2dph": { + "url": "http://127.0.0.1:8630/mcp", + "description": "2dph fact gate. Tool order: search → get → audit. throttled is not absence." + } + } +} diff --git a/docs/picoclaw.md b/docs/picoclaw.md new file mode 100644 index 0000000..06ad39b --- /dev/null +++ b/docs/picoclaw.md @@ -0,0 +1,18 @@ +# PicoClaw profile (reference agent) + +2dph is the memory/fact gate. PicoClaw (or any MCP client) is the agent loop +and is **not** shipped in this repo. + +```bash +docker compose --profile picoclaw up brain-mcp +``` + +The API listens on `127.0.0.1:8630`. Point the agent at +`http://127.0.0.1:8630/mcp` using [deploy/picoclaw/mcp.json.example](../deploy/picoclaw/mcp.json.example). + +OpenAPI: `GET http://127.0.0.1:8630/openapi.json`. + +Before a factual reply: `search` → `get` → `audit`. `throttled` is not a +negative finding. See `skills/picoclaw/SKILL.md`. + +No Cursor required. A live PicoClaw binary/image is an operator choice. diff --git a/skills/picoclaw/SKILL.md b/skills/picoclaw/SKILL.md index 656d9e5..86ba854 100644 --- a/skills/picoclaw/SKILL.md +++ b/skills/picoclaw/SKILL.md @@ -9,7 +9,8 @@ description: >- # PicoClaw — fact-check before assert PicoClaw (or any agent) speaks MCP at `POST /mcp` on `bin/brain/serve.go`. -2dph does not run the agent loop. Compose profile for a live agent is issue #9. +2dph does not run the agent loop. Compose: `docker compose --profile picoclaw up brain-mcp` +(see [docs/picoclaw.md](../../docs/picoclaw.md)). ## Tool order (before a factual reply)