Some checks failed
CI / test (push) Failing after 3s
CLAUDE.md reflects new Options{} constructor, notification
broadcasting, and channel events. CI runs tests with Codecov.
Co-Authored-By: Virgil <virgil@lethean.io>
129 lines
3.4 KiB
Markdown
129 lines
3.4 KiB
Markdown
# CLAUDE.md
|
|
|
|
Guidance for Claude Code and Codex when working with this repository.
|
|
|
|
## Module
|
|
|
|
`forge.lthn.ai/core/mcp` — Model Context Protocol server with file operations, tool registration, notification broadcasting, and channel events.
|
|
|
|
Licence: EUPL-1.2
|
|
|
|
## Build & Test
|
|
|
|
```bash
|
|
go test ./pkg/mcp/... # run all tests
|
|
go build ./pkg/mcp/... # verify compilation
|
|
go build ./cmd/core-mcp/ # build binary
|
|
```
|
|
|
|
Or via the Core CLI:
|
|
|
|
```bash
|
|
core go test
|
|
core go qa # fmt + vet + lint + test
|
|
```
|
|
|
|
## API Shape
|
|
|
|
Uses `Options{}` struct, not functional options:
|
|
|
|
```go
|
|
svc, err := mcp.New(mcp.Options{
|
|
WorkspaceRoot: "/path/to/project",
|
|
ProcessService: ps,
|
|
WSHub: hub,
|
|
Subsystems: []mcp.Subsystem{brain, agentic, monitor},
|
|
})
|
|
```
|
|
|
|
**Do not use:** `WithWorkspaceRoot`, `WithSubsystem`, `WithProcessService`, `WithWSHub` — these no longer exist.
|
|
|
|
## Notification Broadcasting
|
|
|
|
```go
|
|
// Broadcast to all connected sessions
|
|
svc.SendNotificationToAllClients(ctx, "info", "monitor", data)
|
|
|
|
// Push a named channel event
|
|
svc.ChannelSend(ctx, "agent.complete", map[string]any{"repo": "go-io"})
|
|
|
|
// Push to a specific session
|
|
svc.ChannelSendToSession(ctx, session, "build.failed", data)
|
|
```
|
|
|
|
The `claude/channel` experimental capability is registered automatically.
|
|
|
|
## Tool Groups
|
|
|
|
| File | Group | Tools |
|
|
|------|-------|-------|
|
|
| `mcp.go` | files, language | file_read, file_write, file_delete, file_rename, file_exists, file_edit, dir_list, dir_create, lang_detect, lang_list |
|
|
| `tools_metrics.go` | metrics | metrics_record, metrics_query |
|
|
| `tools_process.go` | process | process_start, process_stop, process_kill, process_list, process_output, process_input |
|
|
| `tools_rag.go` | rag | rag_query, rag_ingest, rag_collections |
|
|
| `tools_webview.go` | webview | webview_connect, webview_navigate, etc. |
|
|
| `tools_ws.go` | ws | ws_start, ws_info |
|
|
|
|
## Subsystems
|
|
|
|
| Package | Name | Purpose |
|
|
|---------|------|---------|
|
|
| `pkg/mcp/brain/` | brain | OpenBrain recall, remember, forget |
|
|
| `pkg/mcp/ide/` | ide | IDE bridge to Laravel backend |
|
|
| `pkg/mcp/agentic/` | agentic | Dispatch, status, plans, PRs, scans |
|
|
|
|
## Adding a New Tool
|
|
|
|
```go
|
|
// 1. Define Input/Output structs
|
|
type MyInput struct {
|
|
Name string `json:"name"`
|
|
}
|
|
type MyOutput struct {
|
|
Result string `json:"result"`
|
|
}
|
|
|
|
// 2. Write handler
|
|
func (s *Service) myTool(ctx context.Context, req *mcp.CallToolRequest, input MyInput) (*mcp.CallToolResult, MyOutput, error) {
|
|
return nil, MyOutput{Result: "done"}, nil
|
|
}
|
|
|
|
// 3. Register in registerTools()
|
|
addToolRecorded(s, server, "group", &mcp.Tool{
|
|
Name: "my_tool",
|
|
Description: "Does something useful",
|
|
}, s.myTool)
|
|
```
|
|
|
|
## Adding a New Subsystem
|
|
|
|
```go
|
|
type MySubsystem struct{}
|
|
|
|
func (m *MySubsystem) Name() string { return "my-sub" }
|
|
func (m *MySubsystem) RegisterTools(server *mcp.Server) {
|
|
// register tools here
|
|
}
|
|
|
|
// Register via Options
|
|
svc, err := mcp.New(mcp.Options{
|
|
Subsystems: []mcp.Subsystem{&MySubsystem{}},
|
|
})
|
|
```
|
|
|
|
Subsystems that need to push channel events implement `SubsystemWithNotifier`.
|
|
|
|
## Transports
|
|
|
|
Selected by `Run()` in priority order:
|
|
1. Streamable HTTP (`MCP_HTTP_ADDR` env) — Bearer auth via `MCP_AUTH_TOKEN`
|
|
2. TCP (`MCP_ADDR` env)
|
|
3. Stdio (default) — used by Claude Code / IDEs
|
|
|
|
## Test Naming
|
|
|
|
`_Good` (happy path), `_Bad` (expected errors), `_Ugly` (panics/edge cases).
|
|
|
|
## Go Workspace
|
|
|
|
Part of `~/Code/go.work`. Use `GOWORK=off` to test in isolation.
|