[agent/claude:opus] DX audit and fix. 1) Review CLAUDE.md — update any outdate... #1

Merged
Virgil merged 1 commit from agent/dx-audit-and-fix--1--review-claude-md into main 2026-03-17 09:04:32 +00:00
8 changed files with 559 additions and 13 deletions

View file

@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project overview
Core MCP is a Model Context Protocol implementation in two halves: a **Go binary** (`core-mcp`) that speaks native MCP over stdio/TCP/Unix, and a **PHP Laravel package** (`lthn/mcp`) that adds an HTTP MCP API with auth, quotas, and analytics. Both halves bridge to each other via REST or WebSocket.
Core MCP is a Model Context Protocol implementation in two halves: a **Go binary** (`core-mcp`) that speaks native MCP over stdio/TCP/HTTP/Unix, and a **PHP Laravel package** (`lthn/mcp`) that adds an HTTP MCP API with auth, quotas, and analytics. Both halves bridge to each other via REST or WebSocket.
Module: `forge.lthn.ai/core/mcp` | Licence: EUPL-1.2
@ -39,9 +39,11 @@ composer lint # Laravel Pint (PSR-12)
### Running locally
```bash
./core-mcp mcp serve # Stdio transport (Claude Code / IDE)
./core-mcp mcp serve --workspace /path/to/project # Sandbox file ops to directory
MCP_ADDR=127.0.0.1:9100 ./core-mcp mcp serve # TCP transport
./core-mcp mcp serve # Stdio transport (Claude Code / IDE)
./core-mcp mcp serve --workspace /path/to/project # Sandbox file ops to directory
MCP_ADDR=127.0.0.1:9100 ./core-mcp mcp serve # TCP transport
MCP_HTTP_ADDR=127.0.0.1:9101 ./core-mcp mcp serve # Streamable HTTP transport
MCP_HTTP_ADDR=:9101 MCP_AUTH_TOKEN=secret ./core-mcp mcp serve # HTTP with Bearer auth
```
## Architecture
@ -61,11 +63,15 @@ MCP_ADDR=127.0.0.1:9100 ./core-mcp mcp serve # TCP transport
- `ws``tools_ws.go` (requires `WithWSHub`)
**Subsystem interface** (`Subsystem` / `SubsystemWithShutdown`): Pluggable tool groups registered via `WithSubsystem`. Three ship with the repo:
- `tools_ml.go` — ML inference subsystem (generate, score, probe, status, backends)
- `pkg/mcp/ide/` — IDE bridge to Laravel backend over WebSocket (chat, build, dashboard tools)
- `pkg/mcp/brain/` — OpenBrain knowledge store proxy (remember, recall, forget, list)
- `pkg/mcp/agentic/` — Agent orchestration (prep workspace, dispatch, resume, status, plans, PRs, epics, scan)
**Transports**: stdio (default), TCP (`MCP_ADDR` env var), Unix socket (`ServeUnix`). TCP binds `127.0.0.1` by default; `0.0.0.0` emits a security warning.
**Transports** (selected by `Run()` in priority order):
1. Streamable HTTP (`MCP_HTTP_ADDR` env var) — Bearer token auth via `MCP_AUTH_TOKEN`, endpoint at `/mcp`
2. TCP (`MCP_ADDR` env var) — binds `127.0.0.1` by default; `0.0.0.0` emits a security warning
3. Stdio (default) — used by Claude Code / IDEs
4. Unix socket (`ServeUnix`) — programmatic use only
**REST bridge**: `BridgeToAPI` maps each `ToolRecord` to a `POST` endpoint via `api.ToolBridge`. 10 MB body limit.

View file

@ -255,7 +255,7 @@ func (s *PrepSubsystem) planDelete(_ context.Context, _ *mcp.CallToolRequest, in
}
path := planPath(s.plansDir(), input.ID)
if _, err := os.Stat(path); err != nil {
if !coreio.Local.IsFile(path) {
return nil, PlanDeleteOutput{}, coreerr.E("planDelete", "plan not found: "+input.ID, nil)
}
@ -275,7 +275,7 @@ func (s *PrepSubsystem) planList(_ context.Context, _ *mcp.CallToolRequest, inpu
return nil, PlanListOutput{}, coreerr.E("planList", "failed to access plans directory", err)
}
entries, err := os.ReadDir(dir)
entries, err := coreio.Local.List(dir)
if err != nil {
return nil, PlanListOutput{}, coreerr.E("planList", "failed to read plans directory", err)
}

View file

@ -13,6 +13,7 @@ import (
"path/filepath"
"strings"
coreio "forge.lthn.ai/core/go-io"
coreerr "forge.lthn.ai/core/go-log"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
@ -58,7 +59,7 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
wsDir := filepath.Join(home, "Code", "host-uk", "core", ".core", "workspace", input.Workspace)
srcDir := filepath.Join(wsDir, "src")
if _, err := os.Stat(srcDir); err != nil {
if _, err := coreio.Local.List(srcDir); err != nil {
return nil, CreatePROutput{}, coreerr.E("createPR", "workspace not found: "+input.Workspace, nil)
}

View file

@ -108,7 +108,7 @@ func (s *PrepSubsystem) countRunningByAgent(agent string) int {
home, _ := os.UserHomeDir()
wsRoot := filepath.Join(home, "Code", "host-uk", "core", ".core", "workspace")
entries, err := os.ReadDir(wsRoot)
entries, err := coreio.Local.List(wsRoot)
if err != nil {
return 0
}
@ -167,7 +167,7 @@ func (s *PrepSubsystem) drainQueue() {
home, _ := os.UserHomeDir()
wsRoot := filepath.Join(home, "Code", "host-uk", "core", ".core", "workspace")
entries, err := os.ReadDir(wsRoot)
entries, err := coreio.Local.List(wsRoot)
if err != nil {
return
}

View file

@ -50,7 +50,7 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
srcDir := filepath.Join(wsDir, "src")
// Verify workspace exists
if _, err := os.Stat(srcDir); err != nil {
if _, err := coreio.Local.List(srcDir); err != nil {
return nil, ResumeOutput{}, coreerr.E("resume", "workspace not found: "+input.Workspace, nil)
}

View file

@ -98,7 +98,7 @@ func (s *PrepSubsystem) status(ctx context.Context, _ *mcp.CallToolRequest, inpu
home, _ := os.UserHomeDir()
wsRoot := filepath.Join(home, "Code", "host-uk", "core", ".core", "workspace")
entries, err := os.ReadDir(wsRoot)
entries, err := coreio.Local.List(wsRoot)
if err != nil {
return nil, StatusOutput{}, coreerr.E("status", "no workspaces found", err)
}

View file

@ -0,0 +1,305 @@
// SPDX-License-Identifier: EUPL-1.2
package brain
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
// newTestDirect creates a DirectSubsystem pointing at a test server.
func newTestDirect(url string) *DirectSubsystem {
return &DirectSubsystem{
apiURL: url,
apiKey: "test-key",
client: http.DefaultClient,
}
}
// --- DirectSubsystem interface tests ---
func TestDirectSubsystem_Good_Name(t *testing.T) {
s := &DirectSubsystem{}
if s.Name() != "brain" {
t.Errorf("expected Name() = 'brain', got %q", s.Name())
}
}
func TestDirectSubsystem_Good_Shutdown(t *testing.T) {
s := &DirectSubsystem{}
if err := s.Shutdown(context.Background()); err != nil {
t.Errorf("Shutdown failed: %v", err)
}
}
// --- apiCall tests ---
func TestApiCall_Good_PostWithBody(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("expected POST, got %s", r.Method)
}
if r.Header.Get("Authorization") != "Bearer test-key" {
t.Errorf("missing or wrong Authorization header")
}
if r.Header.Get("Content-Type") != "application/json" {
t.Errorf("missing Content-Type header")
}
w.WriteHeader(200)
json.NewEncoder(w).Encode(map[string]any{"id": "mem-123", "success": true})
}))
defer srv.Close()
s := newTestDirect(srv.URL)
result, err := s.apiCall(context.Background(), "POST", "/v1/brain/remember", map[string]string{"content": "test"})
if err != nil {
t.Fatalf("apiCall failed: %v", err)
}
if result["id"] != "mem-123" {
t.Errorf("expected id=mem-123, got %v", result["id"])
}
}
func TestApiCall_Good_GetNilBody(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
t.Errorf("expected GET, got %s", r.Method)
}
w.WriteHeader(200)
json.NewEncoder(w).Encode(map[string]any{"status": "ok"})
}))
defer srv.Close()
s := newTestDirect(srv.URL)
result, err := s.apiCall(context.Background(), "GET", "/status", nil)
if err != nil {
t.Fatalf("apiCall failed: %v", err)
}
if result["status"] != "ok" {
t.Errorf("expected status=ok, got %v", result["status"])
}
}
func TestApiCall_Bad_NoApiKey(t *testing.T) {
s := &DirectSubsystem{apiKey: "", client: http.DefaultClient}
_, err := s.apiCall(context.Background(), "GET", "/test", nil)
if err == nil {
t.Error("expected error when apiKey is empty")
}
}
func TestApiCall_Bad_HttpError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
w.Write([]byte(`{"error":"internal server error"}`))
}))
defer srv.Close()
s := newTestDirect(srv.URL)
_, err := s.apiCall(context.Background(), "POST", "/fail", map[string]string{})
if err == nil {
t.Error("expected error on HTTP 500")
}
}
func TestApiCall_Bad_InvalidJson(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write([]byte("not json"))
}))
defer srv.Close()
s := newTestDirect(srv.URL)
_, err := s.apiCall(context.Background(), "GET", "/bad-json", nil)
if err == nil {
t.Error("expected error on invalid JSON response")
}
}
func TestApiCall_Bad_Unreachable(t *testing.T) {
s := &DirectSubsystem{
apiURL: "http://127.0.0.1:1", // nothing listening
apiKey: "key",
client: http.DefaultClient,
}
_, err := s.apiCall(context.Background(), "GET", "/test", nil)
if err == nil {
t.Error("expected error for unreachable server")
}
}
// --- remember tool tests ---
func TestDirectRemember_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body map[string]any
json.NewDecoder(r.Body).Decode(&body)
if body["content"] != "test memory" {
t.Errorf("unexpected content: %v", body["content"])
}
if body["agent_id"] != "cladius" {
t.Errorf("expected agent_id=cladius, got %v", body["agent_id"])
}
w.WriteHeader(200)
json.NewEncoder(w).Encode(map[string]any{"id": "mem-456"})
}))
defer srv.Close()
s := newTestDirect(srv.URL)
_, out, err := s.remember(context.Background(), nil, RememberInput{
Content: "test memory",
Type: "observation",
Project: "test-project",
})
if err != nil {
t.Fatalf("remember failed: %v", err)
}
if !out.Success {
t.Error("expected success=true")
}
if out.MemoryID != "mem-456" {
t.Errorf("expected memoryId=mem-456, got %q", out.MemoryID)
}
}
func TestDirectRemember_Bad_ApiError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(422)
w.Write([]byte(`{"error":"validation failed"}`))
}))
defer srv.Close()
s := newTestDirect(srv.URL)
_, _, err := s.remember(context.Background(), nil, RememberInput{Content: "x", Type: "bug"})
if err == nil {
t.Error("expected error on API failure")
}
}
// --- recall tool tests ---
func TestDirectRecall_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body map[string]any
json.NewDecoder(r.Body).Decode(&body)
if body["query"] != "scoring algorithm" {
t.Errorf("unexpected query: %v", body["query"])
}
w.WriteHeader(200)
json.NewEncoder(w).Encode(map[string]any{
"memories": []any{
map[string]any{
"id": "mem-1",
"content": "scoring uses weighted average",
"type": "architecture",
"project": "eaas",
"agent_id": "virgil",
"score": 0.92,
"created_at": "2026-03-01T00:00:00Z",
},
},
})
}))
defer srv.Close()
s := newTestDirect(srv.URL)
_, out, err := s.recall(context.Background(), nil, RecallInput{
Query: "scoring algorithm",
TopK: 5,
Filter: RecallFilter{Project: "eaas"},
})
if err != nil {
t.Fatalf("recall failed: %v", err)
}
if !out.Success || out.Count != 1 {
t.Errorf("expected 1 memory, got %d", out.Count)
}
if out.Memories[0].ID != "mem-1" {
t.Errorf("expected id=mem-1, got %q", out.Memories[0].ID)
}
if out.Memories[0].Confidence != 0.92 {
t.Errorf("expected score=0.92, got %f", out.Memories[0].Confidence)
}
}
func TestDirectRecall_Good_DefaultTopK(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body map[string]any
json.NewDecoder(r.Body).Decode(&body)
// TopK=0 should default to 10
if topK, ok := body["top_k"].(float64); !ok || topK != 10 {
t.Errorf("expected top_k=10, got %v", body["top_k"])
}
w.WriteHeader(200)
json.NewEncoder(w).Encode(map[string]any{"memories": []any{}})
}))
defer srv.Close()
s := newTestDirect(srv.URL)
_, out, err := s.recall(context.Background(), nil, RecallInput{Query: "test"})
if err != nil {
t.Fatalf("recall failed: %v", err)
}
if !out.Success || out.Count != 0 {
t.Errorf("expected empty result, got %d", out.Count)
}
}
func TestDirectRecall_Bad_ApiError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
w.Write([]byte(`{"error":"internal"}`))
}))
defer srv.Close()
s := newTestDirect(srv.URL)
_, _, err := s.recall(context.Background(), nil, RecallInput{Query: "test"})
if err == nil {
t.Error("expected error on API failure")
}
}
// --- forget tool tests ---
func TestDirectForget_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" {
t.Errorf("expected DELETE, got %s", r.Method)
}
if r.URL.Path != "/v1/brain/forget/mem-789" {
t.Errorf("unexpected path: %s", r.URL.Path)
}
w.WriteHeader(200)
json.NewEncoder(w).Encode(map[string]any{"success": true})
}))
defer srv.Close()
s := newTestDirect(srv.URL)
_, out, err := s.forget(context.Background(), nil, ForgetInput{
ID: "mem-789",
Reason: "outdated",
})
if err != nil {
t.Fatalf("forget failed: %v", err)
}
if !out.Success || out.Forgotten != "mem-789" {
t.Errorf("unexpected output: %+v", out)
}
}
func TestDirectForget_Bad_ApiError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
w.Write([]byte(`{"error":"not found"}`))
}))
defer srv.Close()
s := newTestDirect(srv.URL)
_, _, err := s.forget(context.Background(), nil, ForgetInput{ID: "nonexistent"})
if err == nil {
t.Error("expected error on 404")
}
}

View file

@ -0,0 +1,234 @@
// SPDX-License-Identifier: EUPL-1.2
package mcp
import (
"context"
"fmt"
"net"
"net/http"
"os"
"testing"
"time"
)
func TestServeHTTP_Good_HealthEndpoint(t *testing.T) {
s, err := New()
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Get a free port
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Failed to find free port: %v", err)
}
addr := listener.Addr().String()
listener.Close()
errCh := make(chan error, 1)
go func() {
errCh <- s.ServeHTTP(ctx, addr)
}()
// Wait for server to start
time.Sleep(100 * time.Millisecond)
resp, err := http.Get(fmt.Sprintf("http://%s/health", addr))
if err != nil {
t.Fatalf("health check failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Errorf("expected 200, got %d", resp.StatusCode)
}
cancel()
<-errCh
}
func TestServeHTTP_Good_DefaultAddr(t *testing.T) {
if DefaultHTTPAddr != "127.0.0.1:9101" {
t.Errorf("expected default HTTP addr 127.0.0.1:9101, got %s", DefaultHTTPAddr)
}
}
func TestServeHTTP_Good_AuthRequired(t *testing.T) {
os.Setenv("MCP_AUTH_TOKEN", "test-secret-token")
defer os.Unsetenv("MCP_AUTH_TOKEN")
s, err := New()
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Failed to find free port: %v", err)
}
addr := listener.Addr().String()
listener.Close()
errCh := make(chan error, 1)
go func() {
errCh <- s.ServeHTTP(ctx, addr)
}()
time.Sleep(100 * time.Millisecond)
// Request without token should be rejected
resp, err := http.Get(fmt.Sprintf("http://%s/mcp", addr))
if err != nil {
t.Fatalf("request failed: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 401 {
t.Errorf("expected 401 without token, got %d", resp.StatusCode)
}
// Health endpoint should still work (no auth)
resp, err = http.Get(fmt.Sprintf("http://%s/health", addr))
if err != nil {
t.Fatalf("health check failed: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 200 {
t.Errorf("expected 200 for health, got %d", resp.StatusCode)
}
cancel()
<-errCh
}
func TestWithAuth_Good_ValidToken(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
})
wrapped := withAuth("my-token", handler)
// Valid token
req, _ := http.NewRequest("GET", "/", nil)
req.Header.Set("Authorization", "Bearer my-token")
rr := &fakeResponseWriter{code: 200}
wrapped.ServeHTTP(rr, req)
if rr.code != 200 {
t.Errorf("expected 200 with valid token, got %d", rr.code)
}
}
func TestWithAuth_Bad_InvalidToken(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
})
wrapped := withAuth("my-token", handler)
// Wrong token
req, _ := http.NewRequest("GET", "/", nil)
req.Header.Set("Authorization", "Bearer wrong-token")
rr := &fakeResponseWriter{code: 200}
wrapped.ServeHTTP(rr, req)
if rr.code != 401 {
t.Errorf("expected 401 with wrong token, got %d", rr.code)
}
}
func TestWithAuth_Bad_MissingToken(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
})
wrapped := withAuth("my-token", handler)
// No Authorization header
req, _ := http.NewRequest("GET", "/", nil)
rr := &fakeResponseWriter{code: 200}
wrapped.ServeHTTP(rr, req)
if rr.code != 401 {
t.Errorf("expected 401 with missing token, got %d", rr.code)
}
}
func TestWithAuth_Good_EmptyTokenPassthrough(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
})
// Empty token disables auth
wrapped := withAuth("", handler)
req, _ := http.NewRequest("GET", "/", nil)
rr := &fakeResponseWriter{code: 200}
wrapped.ServeHTTP(rr, req)
if rr.code != 200 {
t.Errorf("expected 200 with auth disabled, got %d", rr.code)
}
}
func TestRun_Good_HTTPTrigger(t *testing.T) {
s, err := New()
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Find a free port
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Failed to find free port: %v", err)
}
addr := listener.Addr().String()
listener.Close()
// MCP_HTTP_ADDR takes priority over MCP_ADDR
os.Setenv("MCP_HTTP_ADDR", addr)
os.Setenv("MCP_ADDR", "")
defer os.Unsetenv("MCP_HTTP_ADDR")
defer os.Unsetenv("MCP_ADDR")
errCh := make(chan error, 1)
go func() {
errCh <- s.Run(ctx)
}()
time.Sleep(100 * time.Millisecond)
// Verify server is running
resp, err := http.Get(fmt.Sprintf("http://%s/health", addr))
if err != nil {
t.Fatalf("health check failed: %v", err)
}
resp.Body.Close()
if resp.StatusCode != 200 {
t.Errorf("expected 200, got %d", resp.StatusCode)
}
cancel()
<-errCh
}
// fakeResponseWriter is a minimal http.ResponseWriter for unit testing withAuth.
type fakeResponseWriter struct {
code int
hdr http.Header
}
func (f *fakeResponseWriter) Header() http.Header {
if f.hdr == nil {
f.hdr = make(http.Header)
}
return f.hdr
}
func (f *fakeResponseWriter) Write(b []byte) (int, error) { return len(b), nil }
func (f *fakeResponseWriter) WriteHeader(code int) { f.code = code }