Skip to content

Commit 8166144

Browse files
committed
Add MCP
1 parent 95a8350 commit 8166144

File tree

7 files changed

+313
-94
lines changed

7 files changed

+313
-94
lines changed

agent/agentclaude/agentclaude.go

Lines changed: 75 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package agentclaude
33
import (
44
"context"
55
"encoding/json"
6-
"fmt"
76
"io"
87
"os"
98
"os/exec"
@@ -13,34 +12,42 @@ import (
1312
"time"
1413

1514
"github.com/spf13/afero"
15+
"golang.org/x/xerrors"
1616
)
1717

18-
func New(ctx context.Context, apiKey, systemPrompt, taskPrompt string, onPause func()) error {
18+
func New(ctx context.Context, apiKey, systemPrompt, taskPrompt string, onWaiting func(waiting bool)) error {
1919
claudePath, err := exec.LookPath("claude")
2020
if err != nil {
21-
return fmt.Errorf("claude not found: %w", err)
21+
return xerrors.Errorf("claude not found: %w", err)
2222
}
2323
fs := afero.NewOsFs()
24-
err = injectClaudeMD(fs, `You are an AI agent in a Coder Workspace.
24+
err = injectClaudeMD(fs, `YOU MUST REPORT YOUR STATUS IMMEDIATELY AFTER EACH USER MESSAGE.
2525
26-
The user is running this task entirely autonomously.
26+
INTERRUPT READING FILES OR ANY OTHER TOOL CALL IF YOU HAVE NOT REPORTED A STATUS YET.
2727
28-
Use the coder-agent MCP server to report your progress. You must report when you:
29-
- Start a new task.
30-
- Complete a task (e.g. push a commit, open a PR, etc.)
31-
- Make progress on a task.
28+
You MUST use the mcp__coder-agent__report_status function with all required parameters:
29+
- summary: Short description of what you're doing
30+
- link: A relevant link for the status
31+
- done: Boolean indicating if the task is complete (true/false)
32+
- emoji: Relevant emoji for the status
3233
33-
You should report your progress frequently. Try to report as much as possible.
34+
WHEN TO REPORT (MANDATORY):
35+
1. IMMEDIATELY after receiving ANY user message, before any other actions
36+
2. After completing any task
37+
3. When making significant progress
38+
4. When encountering roadblocks
39+
5. When asking questions
40+
6. Before and after using search tools or making code changes
3441
35-
If you do not, the user will not be able to see your progress.
42+
FAILING TO REPORT STATUS PROPERLY WILL RESULT IN INCORRECT BEHAVIOR.
3643
`, systemPrompt, "")
3744
if err != nil {
38-
return fmt.Errorf("failed to inject claude md: %w", err)
45+
return xerrors.Errorf("failed to inject claude md: %w", err)
3946
}
4047

4148
wd, err := os.Getwd()
4249
if err != nil {
43-
return fmt.Errorf("failed to get working directory: %w", err)
50+
return xerrors.Errorf("failed to get working directory: %w", err)
4451
}
4552

4653
err = configureClaude(fs, ClaudeConfig{
@@ -59,20 +66,22 @@ If you do not, the user will not be able to see your progress.
5966
},
6067
})
6168
if err != nil {
62-
return fmt.Errorf("failed to configure claude: %w", err)
69+
return xerrors.Errorf("failed to configure claude: %w", err)
6370
}
6471

6572
cmd := exec.CommandContext(ctx, claudePath, "--dangerously-skip-permissions", taskPrompt)
6673
// Create a simple wrapper that starts monitoring only after first write
6774
stdoutWriter := &delayedPauseWriter{
6875
writer: os.Stdout,
6976
pauseWindow: 2 * time.Second,
70-
onPause: onPause,
77+
onWaiting: onWaiting,
78+
cooldown: 15 * time.Second,
7179
}
7280
stderrWriter := &delayedPauseWriter{
7381
writer: os.Stderr,
7482
pauseWindow: 2 * time.Second,
75-
onPause: onPause,
83+
onWaiting: onWaiting,
84+
cooldown: 15 * time.Second,
7685
}
7786

7887
cmd.Stdout = stdoutWriter
@@ -86,11 +95,13 @@ If you do not, the user will not be able to see your progress.
8695
type delayedPauseWriter struct {
8796
writer io.Writer
8897
pauseWindow time.Duration
89-
onPause func()
98+
cooldown time.Duration
99+
onWaiting func(waiting bool)
90100
lastWrite time.Time
91101
mu sync.Mutex
92102
started bool
93-
pauseNotified bool
103+
waitingState bool
104+
cooldownUntil time.Time
94105
}
95106

96107
// Write implements io.Writer and starts monitoring on first write
@@ -100,8 +111,12 @@ func (w *delayedPauseWriter) Write(p []byte) (n int, err error) {
100111
w.started = true
101112
w.lastWrite = time.Now()
102113

103-
// Reset pause notification state when new output appears
104-
w.pauseNotified = false
114+
// If we were in waiting state, we're now resumed
115+
if w.waitingState {
116+
w.waitingState = false
117+
w.cooldownUntil = time.Now().Add(w.cooldown)
118+
w.onWaiting(false) // Signal resume
119+
}
105120

106121
w.mu.Unlock()
107122

@@ -113,26 +128,32 @@ func (w *delayedPauseWriter) Write(p []byte) (n int, err error) {
113128
return w.writer.Write(p)
114129
}
115130

116-
// monitorPauses checks for pauses in writing and calls onPause when detected
131+
// monitorPauses checks for pauses in writing and calls onWaiting when detected
117132
func (w *delayedPauseWriter) monitorPauses() {
118133
ticker := time.NewTicker(500 * time.Millisecond)
119134
defer ticker.Stop()
120135

121136
for range ticker.C {
122137
w.mu.Lock()
123-
elapsed := time.Since(w.lastWrite)
124-
alreadyNotified := w.pauseNotified
125138

126-
// If we detect a pause and haven't notified yet, mark as notified
127-
if elapsed >= w.pauseWindow && !alreadyNotified {
128-
w.pauseNotified = true
139+
// Check if we're in a cooldown period
140+
inCooldown := time.Now().Before(w.cooldownUntil)
141+
elapsed := time.Since(w.lastWrite)
142+
shouldWait := elapsed >= w.pauseWindow && !inCooldown
143+
currentState := w.waitingState
144+
shouldNotify := false
145+
146+
// Only update state if it changed
147+
if shouldWait != currentState {
148+
w.waitingState = shouldWait
149+
shouldNotify = true
129150
}
130151

131152
w.mu.Unlock()
132153

133-
// Only notify once per pause period
134-
if elapsed >= w.pauseWindow && !alreadyNotified {
135-
w.onPause()
154+
// Notify outside of the lock to avoid deadlocks
155+
if shouldNotify {
156+
w.onWaiting(shouldWait)
136157
}
137158
}
138159
}
@@ -144,14 +165,14 @@ func injectClaudeMD(fs afero.Fs, coderPrompt, systemPrompt string, configPath st
144165
_, err := fs.Stat(configPath)
145166
if err != nil {
146167
if !os.IsNotExist(err) {
147-
return fmt.Errorf("failed to stat claude config: %w", err)
168+
return xerrors.Errorf("failed to stat claude config: %w", err)
148169
}
149170
}
150171
content := ""
151172
if err == nil {
152173
contentBytes, err := afero.ReadFile(fs, configPath)
153174
if err != nil {
154-
return fmt.Errorf("failed to read claude config: %w", err)
175+
return xerrors.Errorf("failed to read claude config: %w", err)
155176
}
156177
content = string(contentBytes)
157178
}
@@ -202,13 +223,13 @@ func injectClaudeMD(fs afero.Fs, coderPrompt, systemPrompt string, configPath st
202223

203224
err = fs.MkdirAll(filepath.Dir(configPath), 0755)
204225
if err != nil {
205-
return fmt.Errorf("failed to create claude config directory: %w", err)
226+
return xerrors.Errorf("failed to create claude config directory: %w", err)
206227
}
207228

208229
// Write the updated content back to the file
209230
err = afero.WriteFile(fs, configPath, []byte(newContent), 0644)
210231
if err != nil {
211-
return fmt.Errorf("failed to write claude config: %w", err)
232+
return xerrors.Errorf("failed to write claude config: %w", err)
212233
}
213234

214235
return nil
@@ -249,17 +270,17 @@ func configureClaude(fs afero.Fs, cfg ClaudeConfig) error {
249270
if os.IsNotExist(err) {
250271
config = make(map[string]any)
251272
} else {
252-
return fmt.Errorf("failed to stat claude config: %w", err)
273+
return xerrors.Errorf("failed to stat claude config: %w", err)
253274
}
254275
}
255276
if err == nil {
256277
jsonBytes, err := afero.ReadFile(fs, cfg.ConfigPath)
257278
if err != nil {
258-
return fmt.Errorf("failed to read claude config: %w", err)
279+
return xerrors.Errorf("failed to read claude config: %w", err)
259280
}
260281
err = json.Unmarshal(jsonBytes, &config)
261282
if err != nil {
262-
return fmt.Errorf("failed to unmarshal claude config: %w", err)
283+
return xerrors.Errorf("failed to unmarshal claude config: %w", err)
263284
}
264285
}
265286

@@ -312,16 +333,32 @@ func configureClaude(fs afero.Fs, cfg ClaudeConfig) error {
312333
project["mcpServers"] = mcpServers
313334
// Prevents Claude from asking the user to complete the project onboarding.
314335
project["hasCompletedProjectOnboarding"] = true
336+
337+
history, ok := project["history"].([]string)
338+
injectedHistoryLine := "make sure to read claude.md and report tasks properly"
339+
340+
if !ok || len(history) == 0 {
341+
// History doesn't exist or is empty, create it with our injected line
342+
history = []string{injectedHistoryLine}
343+
} else {
344+
// Check if our line is already the first item
345+
if history[0] != injectedHistoryLine {
346+
// Prepend our line to the existing history
347+
history = append([]string{injectedHistoryLine}, history...)
348+
}
349+
}
350+
project["history"] = history
351+
315352
projects[cfg.ProjectDirectory] = project
316353
config["projects"] = projects
317354

318355
jsonBytes, err := json.MarshalIndent(config, "", " ")
319356
if err != nil {
320-
return fmt.Errorf("failed to marshal claude config: %w", err)
357+
return xerrors.Errorf("failed to marshal claude config: %w", err)
321358
}
322359
err = afero.WriteFile(fs, cfg.ConfigPath, jsonBytes, 0644)
323360
if err != nil {
324-
return fmt.Errorf("failed to write claude config: %w", err)
361+
return xerrors.Errorf("failed to write claude config: %w", err)
325362
}
326363
return nil
327364
}

agent/agentmcp/agentmcp.go

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,41 @@ package agentmcp
22

33
import (
44
"context"
5-
"errors"
65

7-
"github.com/coder/coder/v2/buildinfo"
8-
"github.com/coder/coder/v2/codersdk/agentsdk"
96
"github.com/mark3labs/mcp-go/mcp"
107
"github.com/mark3labs/mcp-go/server"
8+
"golang.org/x/xerrors"
9+
10+
"github.com/coder/coder/v2/buildinfo"
11+
"github.com/coder/coder/v2/codersdk/agentsdk"
1112
)
1213

1314
func New(ctx context.Context, sdk *agentsdk.Client) error {
1415
srv := server.NewMCPServer(
15-
"Coder Agent",
16+
"coder-agent",
1617
buildinfo.Version(),
17-
server.WithInstructions(`Report your progress when starting, working on, or completing a task.
18-
19-
You MUST report tasks when starting something new, or when you've completed a task.
20-
21-
You MUST report intermediate progress on a task if you've been working on it for a while.
22-
23-
Examples of sending a task:
24-
- Working on a new part of the codebase.
25-
- Starting on an issue (you should include the issue URL as "link").
26-
- Opening a pull request (you should include the PR URL as "link").
27-
- Completing a task (you should set "done" to true).
28-
- Starting a new task (you should set "done" to false).
18+
server.WithInstructions(`Report your status when starting, working on, or completing a task.
19+
20+
You MUST use the mcp__coder-agent__report_status function with all required parameters:
21+
- summary: Short description of what you're doing
22+
- link: Always use "https://github.com/anthropics/claude-code/issues"
23+
- done: Boolean indicating if task is complete (true/false)
24+
- emoji: Relevant emoji for the task
25+
26+
WHEN TO REPORT (MANDATORY):
27+
1. IMMEDIATELY after receiving ANY user message, before any other actions
28+
2. After completing any task
29+
3. When making significant progress
30+
4. When encountering roadblocks
31+
5. When asking questions
32+
6. Before and after using search tools or making code changes
33+
34+
FAILING TO REPORT TASKS PROPERLY WILL RESULT IN INCORRECT BEHAVIOR.
2935
`),
3036
)
3137

32-
tool := mcp.NewTool("report_task",
33-
mcp.WithDescription(`Report progress on a task.`),
38+
tool := mcp.NewTool("report_status",
39+
mcp.WithDescription(`Report your status or progress on a task.`),
3440
mcp.WithString("summary", mcp.Description(`A summary of your progress on a task.
3541
3642
Good Summaries:
@@ -47,22 +53,22 @@ Good Summaries:
4753

4854
summary, ok := args["summary"].(string)
4955
if !ok {
50-
return nil, errors.New("summary is required")
56+
return nil, xerrors.New("summary is required")
5157
}
5258

5359
link, ok := args["link"].(string)
5460
if !ok {
55-
return nil, errors.New("link is required")
61+
return nil, xerrors.New("link is required")
5662
}
5763

5864
emoji, ok := args["emoji"].(string)
5965
if !ok {
60-
return nil, errors.New("emoji is required")
66+
return nil, xerrors.New("emoji is required")
6167
}
6268

6369
done, ok := args["done"].(bool)
6470
if !ok {
65-
return nil, errors.New("done is required")
71+
return nil, xerrors.New("done is required")
6672
}
6773

6874
err := sdk.PostTask(ctx, agentsdk.PostTaskRequest{

cli/agent.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,9 +441,9 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
441441
Completion: false,
442442
})
443443

444-
return agentclaude.New(inv.Context(), claudeAPIKey, claudeSystemPrompt, claudeTaskPrompt, func() {
444+
return agentclaude.New(inv.Context(), claudeAPIKey, claudeSystemPrompt, claudeTaskPrompt, func(waiting bool) {
445445
_ = client.PatchTasks(inv.Context(), agentsdk.PatchTasksRequest{
446-
WaitingForUserInput: true,
446+
WaitingForUserInput: waiting,
447447
})
448448
})
449449
},

coderd/httpmw/csp.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string, staticAdditions
8888
CSPDirectiveMediaSrc: {"'self'"},
8989
// Report all violations back to the server to log
9090
CSPDirectiveReportURI: {"/api/v2/csp/reports"},
91-
CSPFrameAncestors: {"'none'"},
9291

9392
// Only scripts can manipulate the dom. This prevents someone from
9493
// naming themselves something like '<svg onload="alert(/cross-site-scripting/)" />'.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy