Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

fix(mcp): report task status correctly#17187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
johnstcn merged 1 commit intomainfromcj/mcp/report-task-status
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 57 additions & 15 deletionscli/exp_mcp.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,14 +4,18 @@ import (
"context"
"encoding/json"
"errors"
"log"
"os"
"path/filepath"

"github.com/mark3labs/mcp-go/server"
"golang.org/x/xerrors"

"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
"github.com/coder/coder/v2/buildinfo"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
codermcp "github.com/coder/coder/v2/mcp"
"github.com/coder/serpent"
)
Expand DownExpand Up@@ -191,14 +195,16 @@ func (*RootCmd) mcpConfigureCursor() *serpent.Command {

func (r *RootCmd) mcpServer() *serpent.Command {
var (
client = new(codersdk.Client)
instructions string
allowedTools []string
client = new(codersdk.Client)
instructions string
allowedTools []string
appStatusSlug string
mcpServerAgent bool
)
return &serpent.Command{
Use: "server",
Handler: func(inv *serpent.Invocation) error {
return mcpServerHandler(inv, client, instructions, allowedTools)
return mcpServerHandler(inv, client, instructions, allowedTools, appStatusSlug, mcpServerAgent)
},
Short: "Start the Coder MCP server.",
Middleware: serpent.Chain(
Expand All@@ -209,24 +215,39 @@ func (r *RootCmd) mcpServer() *serpent.Command {
Name: "instructions",
Description: "The instructions to pass to the MCP server.",
Flag: "instructions",
Env: "CODER_MCP_INSTRUCTIONS",
Value: serpent.StringOf(&instructions),
},
{
Name: "allowed-tools",
Description: "Comma-separated list of allowed tools. If not specified, all tools are allowed.",
Flag: "allowed-tools",
Env: "CODER_MCP_ALLOWED_TOOLS",
Value: serpent.StringArrayOf(&allowedTools),
},
{
Name: "app-status-slug",
Description: "When reporting a task, the coder_app slug under which to report the task.",
Flag: "app-status-slug",
Env: "CODER_MCP_APP_STATUS_SLUG",
Value: serpent.StringOf(&appStatusSlug),
Default: "",
},
{
Flag: "agent",
Env: "CODER_MCP_SERVER_AGENT",
Description: "Start the MCP server in agent mode, with a different set of tools.",
Value: serpent.BoolOf(&mcpServerAgent),
},
},
}
}

func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instructions string, allowedTools []string) error {
//nolint:revive // control coupling
func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instructions string, allowedTools []string, appStatusSlug string, mcpServerAgent bool) error {
ctx, cancel := context.WithCancel(inv.Context())
defer cancel()

logger := slog.Make(sloghuman.Sink(inv.Stdout))

me, err := client.User(ctx, codersdk.Me)
if err != nil {
cliui.Errorf(inv.Stderr, "Failed to log in to the Coder deployment.")
Expand All@@ -253,19 +274,40 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
inv.Stderr = invStderr
}()

options := []codermcp.Option{
codermcp.WithInstructions(instructions),
codermcp.WithLogger(&logger),
mcpSrv := server.NewMCPServer(
"Coder Agent",
buildinfo.Version(),
server.WithInstructions(instructions),
)

// Create a separate logger for the tools.
toolLogger := slog.Make(sloghuman.Sink(invStderr))

toolDeps := codermcp.ToolDeps{
Client: client,
Logger: &toolLogger,
AppStatusSlug: appStatusSlug,
AgentClient: agentsdk.New(client.URL),
}

if mcpServerAgent {
// Get the workspace agent token from the environment.
agentToken, ok := os.LookupEnv("CODER_AGENT_TOKEN")
if !ok || agentToken == "" {
return xerrors.New("CODER_AGENT_TOKEN is not set")
}
toolDeps.AgentClient.SetSessionToken(agentToken)
}

// Add allowed tools option if specified
// Register tools based on the allowlist (if specified)
reg := codermcp.AllTools()
if len(allowedTools) > 0 {
options =append(options, codermcp.WithAllowedTools(allowedTools))
reg =reg.WithOnlyAllowed(allowedTools...)
}

srv := codermcp.NewStdio(client, options...)
srv.SetErrorLogger(log.New(invStderr, "", log.LstdFlags))
reg.Register(mcpSrv, toolDeps)

srv := server.NewStdioServer(mcpSrv)
done := make(chan error)
go func() {
defer close(done)
Expand Down
135 changes: 46 additions & 89 deletionsmcp/mcp.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,6 @@ import (
"encoding/json"
"errors"
"io"
"os"
"slices"
"strings"
"time"
Expand All@@ -17,76 +16,12 @@ import (
"golang.org/x/xerrors"

"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
"github.com/coder/coder/v2/buildinfo"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
)

type mcpOptions struct {
instructions string
logger *slog.Logger
allowedTools []string
}

// Option is a function that configures the MCP server.
type Option func(*mcpOptions)

// WithInstructions sets the instructions for the MCP server.
func WithInstructions(instructions string) Option {
return func(o *mcpOptions) {
o.instructions = instructions
}
}

// WithLogger sets the logger for the MCP server.
func WithLogger(logger *slog.Logger) Option {
return func(o *mcpOptions) {
o.logger = logger
}
}

// WithAllowedTools sets the allowed tools for the MCP server.
func WithAllowedTools(tools []string) Option {
return func(o *mcpOptions) {
o.allowedTools = tools
}
}

// NewStdio creates a new MCP stdio server with the given client and options.
// It is the responsibility of the caller to start and stop the server.
func NewStdio(client *codersdk.Client, opts ...Option) *server.StdioServer {
options := &mcpOptions{
instructions: ``,
logger: ptr.Ref(slog.Make(sloghuman.Sink(os.Stdout))),
}
for _, opt := range opts {
opt(options)
}

mcpSrv := server.NewMCPServer(
"Coder Agent",
buildinfo.Version(),
server.WithInstructions(options.instructions),
)

logger := slog.Make(sloghuman.Sink(os.Stdout))

// Register tools based on the allowed list (if specified)
reg := AllTools()
if len(options.allowedTools) > 0 {
reg = reg.WithOnlyAllowed(options.allowedTools...)
}
reg.Register(mcpSrv, ToolDeps{
Client: client,
Logger: &logger,
})

srv := server.NewStdioServer(mcpSrv)
return srv
}

// allTools is the list of all available tools. When adding a new tool,
// make sure to update this list.
var allTools = ToolRegistry{
Expand DownExpand Up@@ -120,6 +55,8 @@ Choose an emoji that helps the user understand the current phase at a glance.`),
mcp.WithBoolean("done", mcp.Description(`Whether the overall task the user requested is complete.
Set to true only when the entire requested operation is finished successfully.
For multi-step processes, use false until all steps are complete.`), mcp.Required()),
mcp.WithBoolean("need_user_attention", mcp.Description(`Whether the user needs to take action on the task.
Set to true if the task is in a failed state or if the user needs to take action to continue.`), mcp.Required()),
),
MakeHandler: handleCoderReportTask,
},
Expand DownExpand Up@@ -265,8 +202,10 @@ Can be either "start" or "stop".`)),

// ToolDeps contains all dependencies needed by tool handlers
type ToolDeps struct {
Client *codersdk.Client
Logger *slog.Logger
Client *codersdk.Client
AgentClient *agentsdk.Client
Logger *slog.Logger
AppStatusSlug string
}

// ToolHandler associates a tool with its handler creation function
Expand DownExpand Up@@ -313,18 +252,23 @@ func AllTools() ToolRegistry {
}

type handleCoderReportTaskArgs struct {
Summary string `json:"summary"`
Link string `json:"link"`
Emoji string `json:"emoji"`
Done bool `json:"done"`
Summary string `json:"summary"`
Link string `json:"link"`
Emoji string `json:"emoji"`
Done bool `json:"done"`
NeedUserAttention bool `json:"need_user_attention"`
}

// Example payload:
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_report_task", "arguments": {"summary": "I'm working onthe login page.", "link": "https://github.com/coder/coder/pull/1234", "emoji": "🔍", "done": false}}}
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_report_task", "arguments": {"summary": "I need help withthe login page.", "link": "https://github.com/coder/coder/pull/1234", "emoji": "🔍", "done": false, "need_user_attention": true}}}
func handleCoderReportTask(deps ToolDeps) server.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
if deps.Client == nil {
return nil, xerrors.New("developer error: client is required")
if deps.AgentClient == nil {
return nil, xerrors.New("developer error: agent client is required")
}

if deps.AppStatusSlug == "" {
return nil, xerrors.New("No app status slug provided, set CODER_MCP_APP_STATUS_SLUG when running the MCP server to report tasks.")
}

// Convert the request parameters to a json.RawMessage so we can unmarshal
Expand All@@ -334,20 +278,33 @@ func handleCoderReportTask(deps ToolDeps) server.ToolHandlerFunc {
return nil, xerrors.Errorf("failed to unmarshal arguments: %w", err)
}

// TODO: Waiting on support for tasks.
deps.Logger.Info(ctx, "report task tool called", slog.F("summary", args.Summary), slog.F("link", args.Link), slog.F("done", args.Done), slog.F("emoji", args.Emoji))
/*
err := sdk.PostTask(ctx, agentsdk.PostTaskRequest{
Reporter: "claude",
Summary: summary,
URL: link,
Completion: done,
Icon: emoji,
})
if err != nil {
return nil, err
}
*/
deps.Logger.Info(ctx, "report task tool called",
slog.F("summary", args.Summary),
slog.F("link", args.Link),
slog.F("emoji", args.Emoji),
slog.F("done", args.Done),
slog.F("need_user_attention", args.NeedUserAttention),
)

newStatus := agentsdk.PatchAppStatus{
AppSlug: deps.AppStatusSlug,
Message: args.Summary,
URI: args.Link,
Icon: args.Emoji,
NeedsUserAttention: args.NeedUserAttention,
State: codersdk.WorkspaceAppStatusStateWorking,
}

if args.Done {
newStatus.State = codersdk.WorkspaceAppStatusStateComplete
}
if args.NeedUserAttention {
newStatus.State = codersdk.WorkspaceAppStatusStateFailure
}

if err := deps.AgentClient.PatchAppStatus(ctx, newStatus); err != nil {
return nil, xerrors.Errorf("failed to patch app status: %w", err)
}

return &mcp.CallToolResult{
Content: []mcp.Content{
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp