diff --git a/internal/agents/clients.go b/internal/agents/clients.go index 0bf5622..8bd81b6 100644 --- a/internal/agents/clients.go +++ b/internal/agents/clients.go @@ -35,9 +35,9 @@ func (m *Manager) clientShellTools() []mcp.Tool { "The most recently active one is listed first. " + "The human's iPhone is listed too, when byre's app is open on it; it takes only show_page, show_file and drive_page, and the other client tools fail there.", InputSchema: json.RawMessage(`{"type": "object", "properties": {}}`), - Handler: func(ctx context.Context, raw json.RawMessage) (string, error) { + Handler: mcp.Typed(func(ctx context.Context, args struct{}) (string, error) { return m.listClients(), nil - }, + }), }, { Name: "client_bash", @@ -134,7 +134,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool { }, "required": ["client", "op"] }`), - Rich: mcp.TypedRich(func(ctx context.Context, args drivePageArgs) mcp.Result { + Handler: mcp.TypedResult(func(ctx context.Context, args drivePageArgs) mcp.Result { return m.drivePage(ctx, l, args) }), }, @@ -186,7 +186,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool { }, "required": ["client", "path"] }`), - Rich: mcp.TypedRich(m.clientRead), + Handler: mcp.TypedResult(m.clientRead), }, { Name: "client_write", @@ -264,7 +264,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool { }, "required": ["client", "tool"] }`), - Rich: mcp.TypedRich(m.clientHands), + Handler: mcp.TypedResult(m.clientHands), }, }...) } diff --git a/internal/agents/diagram.go b/internal/agents/diagram.go index cc33efc..2cf47fa 100644 --- a/internal/agents/diagram.go +++ b/internal/agents/diagram.go @@ -27,7 +27,7 @@ func (m *Manager) renderDiagramTool() mcp.Tool { }, "required": ["source"] }`), - Rich: mcp.TypedRich(func(ctx context.Context, args struct { + Handler: mcp.TypedResult(func(ctx context.Context, args struct { Source string `json:"source"` }) mcp.Result { png, err := renderDiagram(ctx, args.Source) diff --git a/internal/agents/files.go b/internal/agents/files.go index 20d92b2..6616aaf 100644 --- a/internal/agents/files.go +++ b/internal/agents/files.go @@ -38,7 +38,7 @@ func (m *Manager) fileTools(l *live) []mcp.Tool { }, "required": ["path"] }`), - Rich: mcp.TypedRich(func(ctx context.Context, args readArgs) mcp.Result { + Handler: mcp.TypedResult(func(ctx context.Context, args readArgs) mcp.Result { return m.read(l, args) }), }, diff --git a/internal/agents/random.go b/internal/agents/random.go index d034b1f..72f2f4e 100644 --- a/internal/agents/random.go +++ b/internal/agents/random.go @@ -32,7 +32,7 @@ func (m *Manager) randomChoiceTool() mcp.Tool { }, "required": ["choices"] }`), - Rich: mcp.TypedRich(func(ctx context.Context, args struct { + Handler: mcp.TypedResult(func(ctx context.Context, args struct { Choices []struct { Choice string `json:"choice"` Weight float64 `json:"weight"` diff --git a/internal/agents/ready.go b/internal/agents/ready.go index 0ccc1ab..7146d24 100644 --- a/internal/agents/ready.go +++ b/internal/agents/ready.go @@ -121,7 +121,7 @@ func (m *Manager) cancelReadyTool(l *live) mcp.Tool { Description: "Withdraw your standing ready_for_merge or suggest_discard, when it no longer holds: for example the human raised a problem, or you found more work. " + "The human then no longer sees the task as ready to merge or as suggested for discard; call ready_for_merge or suggest_discard again once it holds.", InputSchema: json.RawMessage(`{"type": "object", "properties": {}}`), - Handler: func(ctx context.Context, raw json.RawMessage) (string, error) { + Handler: mcp.Typed(func(ctx context.Context, args struct{}) (string, error) { err := m.store.ClearReady(ctx, l.taskID, agentActor, "") if errors.Is(err, store.ErrNotFound) { return "", fmt.Errorf("%w: this task has no standing ready_for_merge or suggest_discard", ErrConflict) @@ -130,7 +130,7 @@ func (m *Manager) cancelReadyTool(l *live) mcp.Tool { return "", err } return "Withdrawn: the human no longer sees this task as ready to merge or suggested for discard.", nil - }, + }), } } diff --git a/internal/agents/snapshots.go b/internal/agents/snapshots.go index a8d28a6..af5c680 100644 --- a/internal/agents/snapshots.go +++ b/internal/agents/snapshots.go @@ -264,13 +264,13 @@ func (m *Manager) takeSnapshotTool(l *live) mcp.Tool { Name: "take_snapshot", Description: takeSnapshotDescription + " It snapshots your own task's worktree.", InputSchema: json.RawMessage(`{"type": "object", "properties": {}}`), - Handler: func(ctx context.Context, args json.RawMessage) (string, error) { + Handler: mcp.Typed(func(ctx context.Context, args struct{}) (string, error) { err := m.RequestSnapshot(ctx, l.taskID) if err != nil { return "", err } return "Worktree committed to the task's branch, and snapshot requested. If the project has a review workflow, its reviewers check it against the default branch (a subtask's against its parent's branch); their findings come to you together once the workflow finishes.", nil - }, + }), } } diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 940d460..6d322fc 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -13,35 +13,26 @@ type Tool struct { Name string Description string InputSchema json.RawMessage - Handler func(ctx context.Context, args json.RawMessage) (string, error) - Rich func(ctx context.Context, args json.RawMessage) Result + Handler func(ctx context.Context, args json.RawMessage) Result } -func decode[A any](raw json.RawMessage) (A, error) { - args := *new(A) - err := json.Unmarshal(raw, &args) - if err != nil { - return args, fmt.Errorf("malformed call: %w", err) - } - return args, nil -} - -func Typed[A any](run func(ctx context.Context, args A) (string, error)) func(ctx context.Context, raw json.RawMessage) (string, error) { - return func(ctx context.Context, raw json.RawMessage) (string, error) { - args, err := decode[A](raw) - if err != nil { - return "", err - } - return run(ctx, args) - } -} - -func TypedRich[A any](run func(ctx context.Context, args A) Result) func(ctx context.Context, raw json.RawMessage) Result { - return func(ctx context.Context, raw json.RawMessage) Result { - args, err := decode[A](raw) +func Typed[A any](run func(ctx context.Context, args A) (string, error)) func(ctx context.Context, raw json.RawMessage) Result { + return TypedResult(func(ctx context.Context, args A) Result { + text, err := run(ctx, args) if err != nil { return TextResult(err.Error(), true) } + return TextResult(text, false) + }) +} + +func TypedResult[A any](run func(ctx context.Context, args A) Result) func(ctx context.Context, raw json.RawMessage) Result { + return func(ctx context.Context, raw json.RawMessage) Result { + var args A + err := json.Unmarshal(raw, &args) + if err != nil { + return TextResult(fmt.Sprintf("malformed call: %s", err), true) + } return run(ctx, args) } } @@ -120,7 +111,7 @@ func (t Tool) Call(ctx context.Context, args json.RawMessage) Result { defer cancel() done := make(chan Result, 1) go func() { - done <- t.run(cctx, args) + done <- t.Handler(cctx, args) }() late := time.NewTimer(timeout + Grace) defer late.Stop() @@ -134,17 +125,6 @@ func (t Tool) Call(ctx context.Context, args json.RawMessage) Result { } } -func (t Tool) run(ctx context.Context, args json.RawMessage) Result { - if t.Rich != nil { - return t.Rich(ctx, args) - } - text, err := t.Handler(ctx, args) - if err != nil { - return TextResult(err.Error(), true) - } - return TextResult(text, false) -} - type Server struct { Name string Version string