Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7897e67

Browse files
committed
merge stop and start command into one
1 parent 5d1e9e7 commit 7897e67

File tree

3 files changed

+55
-107
lines changed

3 files changed

+55
-107
lines changed

mcp/tools/tools_coder.go

+14-50
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,9 @@ func handleCoderListTemplates(deps ToolDeps) mcpserver.ToolHandlerFunc {
273273
}
274274

275275
// Example payload:
276-
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_start_workspace", "arguments": {"workspace": "dev"}}}
277-
func handleCoderStartWorkspace(deps ToolDeps) mcpserver.ToolHandlerFunc {
276+
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name":
277+
// "coder_workspace_transition", "arguments": {"workspace": "dev", "transition": "stop"}}}
278+
func handleCoderWorkspaceTransition(deps ToolDeps) mcpserver.ToolHandlerFunc {
278279
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
279280
if deps.Client == nil {
280281
return nil, xerrors.New("developer error: client is required")
@@ -292,59 +293,22 @@ func handleCoderStartWorkspace(deps ToolDeps) mcpserver.ToolHandlerFunc {
292293
return nil, xerrors.Errorf("failed to fetch workspace: %w", err)
293294
}
294295

295-
switch workspace.LatestBuild.Status {
296-
case codersdk.WorkspaceStatusPending, codersdk.WorkspaceStatusStarting, codersdk.WorkspaceStatusRunning, codersdk.WorkspaceStatusCanceling:
297-
return nil, xerrors.Errorf("workspace is %s", workspace.LatestBuild.Status)
298-
}
299-
300-
wb, err := deps.Client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
301-
Transition: codersdk.WorkspaceTransitionStart,
302-
})
303-
if err != nil {
304-
return nil, xerrors.Errorf("failed to start workspace: %w", err)
305-
}
306-
307-
resp := map[string]any{"status": wb.Status, "transition": wb.Transition}
308-
respJSON, err := json.Marshal(resp)
309-
if err != nil {
310-
return nil, xerrors.Errorf("failed to encode workspace build: %w", err)
311-
}
312-
313-
return &mcp.CallToolResult{
314-
Content: []mcp.Content{
315-
mcp.NewTextContent(string(respJSON)),
316-
},
317-
}, nil
318-
}
319-
}
320-
321-
// Example payload:
322-
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_stop_workspace", "arguments": {"workspace": "dev"}}}
323-
func handleCoderStopWorkspace(deps ToolDeps) mcpserver.ToolHandlerFunc {
324-
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
325-
if deps.Client == nil {
326-
return nil, xerrors.New("developer error: client is required")
327-
}
328-
329-
args := request.Params.Arguments
330-
331-
wsArg, ok := args["workspace"].(string)
296+
transition, ok := args["transition"].(string)
332297
if !ok {
333-
return nil, xerrors.New("workspace is required")
298+
return nil, xerrors.New("transition is required")
334299
}
335-
336-
workspace, err := getWorkspaceByIDOrOwnerName(ctx, deps.Client, wsArg)
337-
if err != nil {
338-
return nil, xerrors.Errorf("failed to fetch workspace: %w", err)
339-
}
340-
341-
switch workspace.LatestBuild.Status {
342-
case codersdk.WorkspaceStatusPending, codersdk.WorkspaceStatusStopping, codersdk.WorkspaceStatusStopped, codersdk.WorkspaceStatusCanceling:
343-
return nil, xerrors.Errorf("workspace is %s", workspace.LatestBuild.Status)
300+
wsTransition := codersdk.WorkspaceTransition(transition)
301+
switch wsTransition {
302+
case codersdk.WorkspaceTransitionStart:
303+
case codersdk.WorkspaceTransitionStop:
304+
default:
305+
return nil, xerrors.New("invalid transition")
344306
}
345307

308+
// We're not going to check the workspace status here as it is checked on the
309+
// server side.
346310
wb, err := deps.Client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
347-
Transition: codersdk.WorkspaceTransitionStop,
311+
Transition: wsTransition,
348312
})
349313
if err != nil {
350314
return nil, xerrors.Errorf("failed to stop workspace: %w", err)

mcp/tools/tools_coder_test.go

+27-25
Original file line numberDiff line numberDiff line change
@@ -195,27 +195,6 @@ func TestCoderTools(t *testing.T) {
195195
testutil.RequireJSONEq(t, expected, actual)
196196
})
197197

198-
t.Run("coder_stop_workspace", func(t *testing.T) {
199-
// Given: a separate workspace in the running state
200-
stopWs := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{
201-
OrganizationID: owner.OrganizationID,
202-
OwnerID: member.ID,
203-
}).WithAgent().Do()
204-
205-
// When: the coder_stop_workspace tool is called
206-
ctr := makeJSONRPCRequest(t, "tools/call", "coder_stop_workspace", map[string]any{
207-
"workspace": stopWs.Workspace.ID.String(),
208-
})
209-
210-
pty.WriteLine(ctr)
211-
_ = pty.ReadLine(ctx) // skip the echo
212-
213-
// Then: the response is as expected.
214-
expected := makeJSONRPCTextResponse(t, `{"status":"pending","transition":"stop"}`) // no provisionerd yet
215-
actual := pty.ReadLine(ctx)
216-
testutil.RequireJSONEq(t, expected, actual)
217-
})
218-
219198
// NOTE: this test runs after the list_workspaces tool is called.
220199
t.Run("tool_restrictions", func(t *testing.T) {
221200
// Given: the workspace agent is connected
@@ -256,7 +235,29 @@ func TestCoderTools(t *testing.T) {
256235
require.Contains(t, disallowedToolResponse, "not found")
257236
})
258237

259-
t.Run("coder_start_workspace", func(t *testing.T) {
238+
t.Run("coder_workspace_transition_stop", func(t *testing.T) {
239+
// Given: a separate workspace in the running state
240+
stopWs := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{
241+
OrganizationID: owner.OrganizationID,
242+
OwnerID: member.ID,
243+
}).WithAgent().Do()
244+
245+
// When: the coder_workspace_transition tool is called with a stop transition
246+
ctr := makeJSONRPCRequest(t, "tools/call", "coder_workspace_transition", map[string]any{
247+
"workspace": stopWs.Workspace.ID.String(),
248+
"transition": "stop",
249+
})
250+
251+
pty.WriteLine(ctr)
252+
_ = pty.ReadLine(ctx) // skip the echo
253+
254+
// Then: the response is as expected.
255+
expected := makeJSONRPCTextResponse(t, `{"status":"pending","transition":"stop"}`) // no provisionerd yet
256+
actual := pty.ReadLine(ctx)
257+
testutil.RequireJSONEq(t, expected, actual)
258+
})
259+
260+
t.Run("coder_workspace_transition_start", func(t *testing.T) {
260261
// Given: a separate workspace in the stopped state
261262
stopWs := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{
262263
OrganizationID: owner.OrganizationID,
@@ -265,9 +266,10 @@ func TestCoderTools(t *testing.T) {
265266
Transition: database.WorkspaceTransitionStop,
266267
}).Do()
267268

268-
// When: the coder_start_workspace tool is called
269-
ctr := makeJSONRPCRequest(t, "tools/call", "coder_start_workspace", map[string]any{
270-
"workspace": stopWs.Workspace.ID.String(),
269+
// When: the coder_workspace_transition tool is called with a start transition
270+
ctr := makeJSONRPCRequest(t, "tools/call", "coder_workspace_transition", map[string]any{
271+
"workspace": stopWs.Workspace.ID.String(),
272+
"transition": "start",
271273
})
272274

273275
pty.WriteLine(ctr)

mcp/tools/tools_registry.go

+14-32
Original file line numberDiff line numberDiff line change
@@ -155,52 +155,34 @@ Note: Very long-running commands may time out.`), mcp.Required()),
155155
MakeHandler: handleCoderWorkspaceExec,
156156
},
157157
{
158-
Tool: mcp.NewTool("coder_start_workspace",
159-
mcp.WithDescription(`Start a stopped Coder workspace.
160-
Initiates the workspace build process to provision and start all resources.
161-
Only works on workspaces that are currently stopped or failed.
162-
Starting a workspace is an asynchronous operation - it may take several minutes to complete.
158+
Tool: mcp.NewTool("coder_workspace_transition",
159+
mcp.WithDescription(`Start or stop a running Coder workspace.
160+
If stopping, initiates the workspace stop transition.
161+
Only works on workspaces that are currently running or failed.
163162
164-
After calling this tool:
165-
1. Use coder_report_task to inform the user that the workspace is starting
166-
2. Use coder_get_workspace periodically to check for completion
163+
If starting, initiates the workspace start transition.
164+
Only works on workspaces that are currently stopped or failed.
167165
168-
Common errors:
169-
- Workspace already running/starting: No action needed
170-
- Quota limits exceeded: User may have reached resource limits
171-
- Template error: The underlying template may have issues`),
172-
mcp.WithString("workspace", mcp.Description(`The workspace ID (UUID) or name to start.
173-
Can be specified as either:
174-
- Full UUID: e.g., "8a0b9c7d-1e2f-3a4b-5c6d-7e8f9a0b1c2d"
175-
- Workspace name: e.g., "dev", "python-project"
176-
The workspace must be in a stopped state to be started.
177-
Use coder_get_workspace first to check the current workspace status.`), mcp.Required()),
178-
),
179-
MakeHandler: handleCoderStartWorkspace,
180-
},
181-
{
182-
Tool: mcp.NewTool("coder_stop_workspace",
183-
mcp.WithDescription(`Stop a running Coder workspace.
184-
Initiates the workspace termination process to shut down all resources.
185-
Only works on workspaces that are currently running.
186-
Stopping a workspace is an asynchronous operation - it may take several minutes to complete.
166+
Stopping or starting a workspace is an asynchronous operation - it may take several minutes to complete.
187167
188168
After calling this tool:
189-
1. Use coder_report_task to inform the user that the workspace is stopping
169+
1. Use coder_report_task to inform the user that the workspace is stopping or starting
190170
2. Use coder_get_workspace periodically to check for completion
191171
192172
Common errors:
193-
- Workspace already stopped/stopping: No action needed
173+
- Workspace already started/starting/stopped/stopping: No action needed
194174
- Cancellation failed: There may be issues with the underlying infrastructure
195175
- User doesn't own workspace: Permission issues`),
196-
mcp.WithString("workspace", mcp.Description(`The workspace ID (UUID) or name to stop.
176+
mcp.WithString("workspace", mcp.Description(`The workspace ID (UUID) or name to start or stop.
197177
Can be specified as either:
198178
- Full UUID: e.g., "8a0b9c7d-1e2f-3a4b-5c6d-7e8f9a0b1c2d"
199179
- Workspace name: e.g., "dev", "python-project"
200-
The workspace must be in a running state to be stopped.
180+
The workspace must be in a running state to be stopped, or in a stopped or failed state to be started.
201181
Use coder_get_workspace first to check the current workspace status.`), mcp.Required()),
182+
mcp.WithString("transition", mcp.Description(`The transition to apply to the workspace.
183+
Can be either "start" or "stop".`)),
202184
),
203-
MakeHandler: handleCoderStopWorkspace,
185+
MakeHandler: handleCoderWorkspaceTransition,
204186
},
205187
}
206188

0 commit comments

Comments
 (0)