feat: PicoClaw compose profile exposes brain MCP on localhost (#23)
Tests / Test (push) Failing after 5s
Tests / Release (semver) (push) Skipped

Agent is not shipped. docker compose --profile picoclaw up brain-mcp
and point the client at 127.0.0.1:8630/mcp.
This commit is contained in:
2026-08-13 21:40:40 +01:00
committed by GitHub
co-authored by GitHub
parent e5a982e99a
commit 7fb51e9918
7 changed files with 61 additions and 2 deletions
+1 -1
View File
@@ -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)
+1
View File
@@ -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
```
+12
View File
@@ -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)
+19
View File
@@ -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:
+8
View File
@@ -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."
}
}
}
+18
View File
@@ -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.
+2 -1
View File
@@ -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)