Unify MCP tool handlers around one result type
This commit is contained in:
@@ -35,9 +35,9 @@ func (m *Manager) clientShellTools() []mcp.Tool {
|
|||||||
"The most recently active one is listed first. " +
|
"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.",
|
"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": {}}`),
|
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
|
return m.listClients(), nil
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "client_bash",
|
Name: "client_bash",
|
||||||
@@ -134,7 +134,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
|||||||
},
|
},
|
||||||
"required": ["client", "op"]
|
"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)
|
return m.drivePage(ctx, l, args)
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -186,7 +186,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
|||||||
},
|
},
|
||||||
"required": ["client", "path"]
|
"required": ["client", "path"]
|
||||||
}`),
|
}`),
|
||||||
Rich: mcp.TypedRich(m.clientRead),
|
Handler: mcp.TypedResult(m.clientRead),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "client_write",
|
Name: "client_write",
|
||||||
@@ -264,7 +264,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
|||||||
},
|
},
|
||||||
"required": ["client", "tool"]
|
"required": ["client", "tool"]
|
||||||
}`),
|
}`),
|
||||||
Rich: mcp.TypedRich(m.clientHands),
|
Handler: mcp.TypedResult(m.clientHands),
|
||||||
},
|
},
|
||||||
}...)
|
}...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (m *Manager) renderDiagramTool() mcp.Tool {
|
|||||||
},
|
},
|
||||||
"required": ["source"]
|
"required": ["source"]
|
||||||
}`),
|
}`),
|
||||||
Rich: mcp.TypedRich(func(ctx context.Context, args struct {
|
Handler: mcp.TypedResult(func(ctx context.Context, args struct {
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
}) mcp.Result {
|
}) mcp.Result {
|
||||||
png, err := renderDiagram(ctx, args.Source)
|
png, err := renderDiagram(ctx, args.Source)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func (m *Manager) fileTools(l *live) []mcp.Tool {
|
|||||||
},
|
},
|
||||||
"required": ["path"]
|
"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)
|
return m.read(l, args)
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (m *Manager) randomChoiceTool() mcp.Tool {
|
|||||||
},
|
},
|
||||||
"required": ["choices"]
|
"required": ["choices"]
|
||||||
}`),
|
}`),
|
||||||
Rich: mcp.TypedRich(func(ctx context.Context, args struct {
|
Handler: mcp.TypedResult(func(ctx context.Context, args struct {
|
||||||
Choices []struct {
|
Choices []struct {
|
||||||
Choice string `json:"choice"`
|
Choice string `json:"choice"`
|
||||||
Weight float64 `json:"weight"`
|
Weight float64 `json:"weight"`
|
||||||
|
|||||||
@@ -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. " +
|
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.",
|
"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": {}}`),
|
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, "")
|
err := m.store.ClearReady(ctx, l.taskID, agentActor, "")
|
||||||
if errors.Is(err, store.ErrNotFound) {
|
if errors.Is(err, store.ErrNotFound) {
|
||||||
return "", fmt.Errorf("%w: this task has no standing ready_for_merge or suggest_discard", ErrConflict)
|
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 "", err
|
||||||
}
|
}
|
||||||
return "Withdrawn: the human no longer sees this task as ready to merge or suggested for discard.", nil
|
return "Withdrawn: the human no longer sees this task as ready to merge or suggested for discard.", nil
|
||||||
},
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -264,13 +264,13 @@ func (m *Manager) takeSnapshotTool(l *live) mcp.Tool {
|
|||||||
Name: "take_snapshot",
|
Name: "take_snapshot",
|
||||||
Description: takeSnapshotDescription + " It snapshots your own task's worktree.",
|
Description: takeSnapshotDescription + " It snapshots your own task's worktree.",
|
||||||
InputSchema: json.RawMessage(`{"type": "object", "properties": {}}`),
|
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)
|
err := m.RequestSnapshot(ctx, l.taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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
|
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
|
||||||
},
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-36
@@ -13,35 +13,26 @@ type Tool struct {
|
|||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
InputSchema json.RawMessage
|
InputSchema json.RawMessage
|
||||||
Handler func(ctx context.Context, args json.RawMessage) (string, error)
|
Handler func(ctx context.Context, args json.RawMessage) Result
|
||||||
Rich func(ctx context.Context, args json.RawMessage) Result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func decode[A any](raw json.RawMessage) (A, error) {
|
func Typed[A any](run func(ctx context.Context, args A) (string, error)) func(ctx context.Context, raw json.RawMessage) Result {
|
||||||
args := *new(A)
|
return TypedResult(func(ctx context.Context, args A) Result {
|
||||||
err := json.Unmarshal(raw, &args)
|
text, err := run(ctx, 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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TextResult(err.Error(), true)
|
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)
|
return run(ctx, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,7 +111,7 @@ func (t Tool) Call(ctx context.Context, args json.RawMessage) Result {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
done := make(chan Result, 1)
|
done := make(chan Result, 1)
|
||||||
go func() {
|
go func() {
|
||||||
done <- t.run(cctx, args)
|
done <- t.Handler(cctx, args)
|
||||||
}()
|
}()
|
||||||
late := time.NewTimer(timeout + Grace)
|
late := time.NewTimer(timeout + Grace)
|
||||||
defer late.Stop()
|
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 {
|
type Server struct {
|
||||||
Name string
|
Name string
|
||||||
Version string
|
Version string
|
||||||
|
|||||||
Reference in New Issue
Block a user