@@ -2,7 +2,10 @@ package cli
2
2
3
3
import (
4
4
"context"
5
+ "encoding/json"
5
6
"errors"
7
+ "os"
8
+ "path/filepath"
6
9
7
10
"cdr.dev/slog"
8
11
"cdr.dev/slog/sloggers/sloghuman"
@@ -13,17 +16,184 @@ import (
13
16
)
14
17
15
18
func (r * RootCmd ) mcpCommand () * serpent.Command {
19
+ cmd := & serpent.Command {
20
+ Use : "mcp" ,
21
+ Short : "Run the Coder MCP server and configure it to work with AI tools." ,
22
+ Long : "The Coder MCP server allows you to automatically create workspaces with parameters." ,
23
+ Children : []* serpent.Command {
24
+ r .mcpConfigure (),
25
+ r .mcpServer (),
26
+ },
27
+ }
28
+ return cmd
29
+ }
30
+
31
+ func (r * RootCmd ) mcpConfigure () * serpent.Command {
32
+ cmd := & serpent.Command {
33
+ Use : "configure" ,
34
+ Short : "Automatically configure the MCP server." ,
35
+ Children : []* serpent.Command {
36
+ r .mcpConfigureClaudeDesktop (),
37
+ r .mcpConfigureClaudeCode (),
38
+ r .mcpConfigureCursor (),
39
+ },
40
+ }
41
+ return cmd
42
+ }
43
+
44
+ func (r * RootCmd ) mcpConfigureClaudeDesktop () * serpent.Command {
45
+ cmd := & serpent.Command {
46
+ Use : "claude-desktop" ,
47
+ Short : "Configure the Claude Desktop server." ,
48
+ Handler : func (_ * serpent.Invocation ) error {
49
+ configPath , err := os .UserConfigDir ()
50
+ if err != nil {
51
+ return err
52
+ }
53
+ configPath = filepath .Join (configPath , "Claude" )
54
+ err = os .MkdirAll (configPath , 0755 )
55
+ if err != nil {
56
+ return err
57
+ }
58
+ configPath = filepath .Join (configPath , "claude_desktop_config.json" )
59
+ _ , err = os .Stat (configPath )
60
+ if err != nil {
61
+ if ! os .IsNotExist (err ) {
62
+ return err
63
+ }
64
+ }
65
+ contents := map [string ]any {}
66
+ data , err := os .ReadFile (configPath )
67
+ if err != nil {
68
+ if ! os .IsNotExist (err ) {
69
+ return err
70
+ }
71
+ } else {
72
+ err = json .Unmarshal (data , & contents )
73
+ if err != nil {
74
+ return err
75
+ }
76
+ }
77
+ binPath , err := os .Executable ()
78
+ if err != nil {
79
+ return err
80
+ }
81
+ contents ["mcpServers" ] = map [string ]any {
82
+ "coder" : map [string ]any {"command" : binPath , "args" : []string {"mcp" , "server" }},
83
+ }
84
+ data , err = json .MarshalIndent (contents , "" , " " )
85
+ if err != nil {
86
+ return err
87
+ }
88
+ err = os .WriteFile (configPath , data , 0600 )
89
+ if err != nil {
90
+ return err
91
+ }
92
+ return nil
93
+ },
94
+ }
95
+ return cmd
96
+ }
97
+
98
+ func (_ * RootCmd ) mcpConfigureClaudeCode () * serpent.Command {
99
+ cmd := & serpent.Command {
100
+ Use : "claude-code" ,
101
+ Short : "Configure the Claude Code server." ,
102
+ Handler : func (_ * serpent.Invocation ) error {
103
+ return nil
104
+ },
105
+ }
106
+ return cmd
107
+ }
108
+
109
+ func (_ * RootCmd ) mcpConfigureCursor () * serpent.Command {
110
+ var project bool
111
+ cmd := & serpent.Command {
112
+ Use : "cursor" ,
113
+ Short : "Configure Cursor to use Coder MCP." ,
114
+ Options : serpent.OptionSet {
115
+ serpent.Option {
116
+ Flag : "project" ,
117
+ Env : "CODER_MCP_CURSOR_PROJECT" ,
118
+ Description : "Use to configure a local project to use the Cursor MCP." ,
119
+ Value : serpent .BoolOf (& project ),
120
+ },
121
+ },
122
+ Handler : func (_ * serpent.Invocation ) error {
123
+ dir , err := os .Getwd ()
124
+ if err != nil {
125
+ return err
126
+ }
127
+ if ! project {
128
+ dir , err = os .UserHomeDir ()
129
+ if err != nil {
130
+ return err
131
+ }
132
+ }
133
+ cursorDir := filepath .Join (dir , ".cursor" )
134
+ err = os .MkdirAll (cursorDir , 0755 )
135
+ if err != nil {
136
+ return err
137
+ }
138
+ mcpConfig := filepath .Join (cursorDir , "mcp.json" )
139
+ _ , err = os .Stat (mcpConfig )
140
+ contents := map [string ]any {}
141
+ if err != nil {
142
+ if ! os .IsNotExist (err ) {
143
+ return err
144
+ }
145
+ } else {
146
+ data , err := os .ReadFile (mcpConfig )
147
+ if err != nil {
148
+ return err
149
+ }
150
+ // The config can be empty, so we don't want to return an error if it is.
151
+ if len (data ) > 0 {
152
+ err = json .Unmarshal (data , & contents )
153
+ if err != nil {
154
+ return err
155
+ }
156
+ }
157
+ }
158
+ mcpServers , ok := contents ["mcpServers" ].(map [string ]any )
159
+ if ! ok {
160
+ mcpServers = map [string ]any {}
161
+ }
162
+ binPath , err := os .Executable ()
163
+ if err != nil {
164
+ return err
165
+ }
166
+ mcpServers ["coder" ] = map [string ]any {
167
+ "command" : binPath ,
168
+ "args" : []string {"mcp" , "server" },
169
+ }
170
+ contents ["mcpServers" ] = mcpServers
171
+ data , err := json .MarshalIndent (contents , "" , " " )
172
+ if err != nil {
173
+ return err
174
+ }
175
+ err = os .WriteFile (mcpConfig , data , 0600 )
176
+ if err != nil {
177
+ return err
178
+ }
179
+ return nil
180
+ },
181
+ }
182
+ return cmd
183
+ }
184
+
185
+ func (r * RootCmd ) mcpServer () * serpent.Command {
16
186
var (
17
187
client = new (codersdk.Client )
18
188
instructions string
19
189
allowedTools []string
20
190
)
21
191
return & serpent.Command {
22
- Use : "mcp " ,
192
+ Use : "server " ,
23
193
Handler : func (inv * serpent.Invocation ) error {
24
- return mcpHandler (inv , client , instructions , allowedTools )
194
+ return mcpServerHandler (inv , client , instructions , allowedTools )
25
195
},
26
- Short : "Start an MCP server that can be used to interact with a Coder deployment ." ,
196
+ Short : "Start the Coder MCP server." ,
27
197
Middleware : serpent .Chain (
28
198
r .InitClient (client ),
29
199
),
@@ -44,7 +214,7 @@ func (r *RootCmd) mcpCommand() *serpent.Command {
44
214
}
45
215
}
46
216
47
- func mcpHandler (inv * serpent.Invocation , client * codersdk.Client , instructions string , allowedTools []string ) error {
217
+ func mcpServerHandler (inv * serpent.Invocation , client * codersdk.Client , instructions string , allowedTools []string ) error {
48
218
ctx , cancel := context .WithCancel (inv .Context ())
49
219
defer cancel ()
50
220
0 commit comments