Skip to content

Commit 58d0823

Browse files
authored
Merge pull request #739 from dgageot/read-only-mcp
An agent with only read-only tools is read-only
2 parents 1e4f4f0 + 13d0dee commit 58d0823

File tree

1 file changed

+31
-7
lines changed

1 file changed

+31
-7
lines changed

pkg/mcp/server.go

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/modelcontextprotocol/go-sdk/mcp"
99

10+
"github.com/docker/cagent/pkg/agent"
1011
"github.com/docker/cagent/pkg/agentfile"
1112
"github.com/docker/cagent/pkg/cli"
1213
"github.com/docker/cagent/pkg/config"
@@ -27,7 +28,7 @@ type ToolOutput struct {
2728
}
2829

2930
func StartMCPServer(ctx context.Context, out *cli.Printer, agentFilename string, runConfig config.RuntimeConfig) error {
30-
slog.Debug("Starting MCP server", "agent_ref", agentFilename)
31+
slog.Debug("Starting MCP server", "agent", agentFilename)
3132

3233
agentFilename, err := agentfile.Resolve(ctx, out, agentFilename)
3334
if err != nil {
@@ -54,21 +55,29 @@ func StartMCPServer(ctx context.Context, out *cli.Printer, agentFilename string,
5455
slog.Debug("Adding MCP tools for agents", "count", len(agentNames))
5556

5657
for _, agentName := range agentNames {
57-
agent, err := t.Agent(agentName)
58+
ag, err := t.Agent(agentName)
5859
if err != nil {
5960
return fmt.Errorf("failed to get agent %s: %w", agentName, err)
6061
}
6162

62-
description := agent.Description()
63+
description := ag.Description()
6364
if description == "" {
6465
description = fmt.Sprintf("Run the %s agent", agentName)
6566
}
6667

6768
slog.Debug("Adding MCP tool", "agent", agentName, "description", description)
6869

70+
readOnly, err := isReadOnlyAgent(ctx, ag)
71+
if err != nil {
72+
return fmt.Errorf("failed to determine if agent %s is read-only: %w", agentName, err)
73+
}
74+
6975
toolDef := &mcp.Tool{
70-
Name: agentName,
71-
Description: description,
76+
Name: agentName,
77+
Description: description,
78+
Annotations: &mcp.ToolAnnotations{
79+
ReadOnlyHint: readOnly,
80+
},
7281
InputSchema: tools.MustSchemaFor[ToolInput](),
7382
OutputSchema: tools.MustSchemaFor[ToolOutput](),
7483
}
@@ -89,14 +98,14 @@ func CreateToolHandler(t *team.Team, agentName, agentFilename string) func(conte
8998
return func(ctx context.Context, req *mcp.CallToolRequest, input ToolInput) (*mcp.CallToolResult, ToolOutput, error) {
9099
slog.Debug("MCP tool called", "agent", agentName, "message", input.Message)
91100

92-
agent, err := t.Agent(agentName)
101+
ag, err := t.Agent(agentName)
93102
if err != nil {
94103
return nil, ToolOutput{}, fmt.Errorf("failed to get agent: %w", err)
95104
}
96105

97106
sess := session.New(
98107
session.WithTitle("MCP tool call"),
99-
session.WithMaxIterations(agent.MaxIterations()),
108+
session.WithMaxIterations(ag.MaxIterations()),
100109
session.WithUserMessage(agentFilename, input.Message),
101110
session.WithToolsApproved(true),
102111
)
@@ -125,3 +134,18 @@ func CreateToolHandler(t *team.Team, agentName, agentFilename string) func(conte
125134
return nil, ToolOutput{Response: result}, nil
126135
}
127136
}
137+
138+
func isReadOnlyAgent(ctx context.Context, ag *agent.Agent) (bool, error) {
139+
allTolls, err := ag.Tools(ctx)
140+
if err != nil {
141+
return false, err
142+
}
143+
144+
for _, tool := range allTolls {
145+
if !tool.Annotations.ReadOnlyHint {
146+
return false, nil
147+
}
148+
}
149+
150+
return true, nil
151+
}

0 commit comments

Comments
 (0)