diff --git a/packages/mcp-v2/src/tools/agents/run.ts b/packages/mcp-v2/src/tools/agents/run.ts new file mode 100644 index 00000000000..4ee1b558d30 --- /dev/null +++ b/packages/mcp-v2/src/tools/agents/run.ts @@ -0,0 +1,66 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { createMcpCaller } from "../../caller"; +import { defineTool } from "../../define-tool"; +import { hostServiceMutation } from "../../host-service-client"; + +export function register(server: McpServer): void { + defineTool(server, { + name: "agents_run", + description: + "Launch an agent inside an existing workspace. Resolves the host that owns the workspace, then runs the named agent preset (or HostAgentConfig instance) with the given prompt in a fresh terminal session. Use this to start a second agent in a workspace that already exists; for create-and-spawn in a single call, pass `agents` to workspaces_create instead.", + inputSchema: { + workspaceId: z + .string() + .uuid() + .describe("Workspace UUID to run the agent in."), + agent: z + .string() + .min(1) + .describe( + "Agent preset id (e.g. `claude`, `codex`) or HostAgentConfig instance UUID.", + ), + prompt: z.string().min(1).describe("Prompt sent to the agent."), + attachmentIds: z + .array(z.string().uuid()) + .optional() + .describe( + "Host-scoped attachment UUIDs. The host resolves these to absolute paths and appends them to the prompt.", + ), + }, + handler: async (input, ctx) => { + const caller = createMcpCaller(ctx); + const workspace = await caller.v2Workspace.getFromHost({ + organizationId: ctx.organizationId, + id: input.workspaceId, + }); + if (!workspace) { + throw new Error(`Workspace not found: ${input.workspaceId}`); + } + + return hostServiceMutation< + { + workspaceId: string; + agent: string; + prompt: string; + attachmentIds?: string[]; + }, + { sessionId: string; label: string } + >( + { + relayUrl: ctx.relayUrl, + organizationId: ctx.organizationId, + hostId: workspace.hostId, + jwt: ctx.bearerToken, + }, + "agents.run", + { + workspaceId: input.workspaceId, + agent: input.agent, + prompt: input.prompt, + attachmentIds: input.attachmentIds, + }, + ); + }, + }); +} diff --git a/packages/mcp-v2/src/tools/register.ts b/packages/mcp-v2/src/tools/register.ts index 3b0dd7a76af..bcc8ec0bf58 100644 --- a/packages/mcp-v2/src/tools/register.ts +++ b/packages/mcp-v2/src/tools/register.ts @@ -4,6 +4,7 @@ import { setServerToolCallEmitter, } from "../define-tool"; +import * as agentsRun from "./agents/run"; import * as automationsCreate from "./automations/create"; import * as automationsDelete from "./automations/delete"; import * as automationsGet from "./automations/get"; @@ -46,6 +47,7 @@ const REGISTRARS = [ workspacesList, workspacesCreate, workspacesDelete, + agentsRun, projectsList, hostsList, ]; diff --git a/packages/mcp-v2/src/tools/workspaces/create.ts b/packages/mcp-v2/src/tools/workspaces/create.ts index 016f3f47752..1378a79ea3e 100644 --- a/packages/mcp-v2/src/tools/workspaces/create.ts +++ b/packages/mcp-v2/src/tools/workspaces/create.ts @@ -3,11 +3,27 @@ import { z } from "zod"; import { defineTool } from "../../define-tool"; import { hostServiceMutation } from "../../host-service-client"; +const agentLaunchSchema = z.object({ + agent: z + .string() + .min(1) + .describe( + "Agent preset id (e.g. `claude`, `codex`) or HostAgentConfig instance UUID.", + ), + prompt: z.string().min(1).describe("Initial prompt the agent starts with."), + attachmentIds: z + .array(z.string().uuid()) + .optional() + .describe( + "Host-scoped attachment UUIDs. The host resolves these to absolute paths and appends them to the prompt.", + ), +}); + export function register(server: McpServer): void { defineTool(server, { name: "workspaces_create", description: - "Create a workspace on a host. A workspace is a branch-scoped working copy of a project. The host service materializes the git worktree on disk before returning. Provide exactly one of `branch` or `pr`. Use projects_list and hosts_list first to get the projectId and hostId.", + "Create a workspace on a host. A workspace is a branch-scoped working copy of a project. The host service materializes the git worktree on disk before returning. Provide exactly one of `branch` or `pr`. Optionally pass `agents` to spawn one or more agents in the workspace as soon as it is ready (each entry runs the equivalent of `agents_run` against the new workspace). Use projects_list and hosts_list first to get the projectId and hostId.", inputSchema: { projectId: z.string().uuid().describe("Project UUID."), name: z.string().min(1).describe("Workspace name (display)."), @@ -41,6 +57,12 @@ export function register(server: McpServer): void { .uuid() .optional() .describe("Optional Superset task id to link to the new workspace."), + agents: z + .array(agentLaunchSchema) + .optional() + .describe( + "Agents to spawn in the workspace immediately after creation.", + ), }, handler: async (input, ctx) => { return hostServiceMutation< @@ -51,6 +73,11 @@ export function register(server: McpServer): void { pr?: number; baseBranch?: string; taskId?: string; + agents?: Array<{ + agent: string; + prompt: string; + attachmentIds?: string[]; + }>; }, { workspace: { @@ -60,7 +87,10 @@ export function register(server: McpServer): void { branch: string; }; terminals: Array<{ terminalId: string; label?: string }>; - agents: Array; + agents: Array< + | { ok: true; sessionId: string; label: string } + | { ok: false; error: string } + >; alreadyExists: boolean; } >( @@ -78,6 +108,7 @@ export function register(server: McpServer): void { pr: input.pr, baseBranch: input.baseBranch, taskId: input.taskId, + agents: input.agents, }, ); },