diff --git a/packages/server/src/server/agent/mcp-server.test.ts b/packages/server/src/server/agent/mcp-server.test.ts index 3a90b7f577..33467b22f6 100644 --- a/packages/server/src/server/agent/mcp-server.test.ts +++ b/packages/server/src/server/agent/mcp-server.test.ts @@ -5,7 +5,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; import { describe, expect, it, vi } from "vitest"; import { realpathSync } from "node:fs"; -import { access, mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; +import { access, mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; import { join, resolve as resolvePath } from "node:path"; import { tmpdir } from "node:os"; import { z } from "zod"; @@ -843,6 +843,49 @@ function createAgentMcpServer(options: Parameters { + it("documents only advertised create_workspace input names", async () => { + const server = await createAgentMcpServer({ + agentManager: new BoundaryAgentManagerFake() as AgentManager, + agentStorage: new BoundaryAgentStorageFake() as AgentStorage, + providerSnapshotManager: + new BoundaryProviderSnapshotManagerFake() as unknown as ProviderSnapshotManager, + logger: createTestLogger(), + }); + const client = await connectInMemoryMcpClient(server); + + try { + const skill = await readFile( + new URL("../../../../../skills/paseo/SKILL.md", import.meta.url), + "utf8", + ); + const paragraph = skill.split("\n").find((line) => line.startsWith("**`create_workspace`**")); + expect(paragraph).toBeTypeOf("string"); + // The final sentence describes output, rather than tool inputs. + const inputGuidance = paragraph!.split("Returns ")[0]!; + const documentedInputs = [...inputGuidance.matchAll(/`([a-zA-Z][a-zA-Z0-9]*)`/g)].map( + (match) => match[1]!, + ); + expect(documentedInputs.length).toBeGreaterThan(0); + const { tools } = await client.listTools(); + const tool = tools.find((entry) => entry.name === "create_workspace")!; + const properties = tool.inputSchema.properties!; + const advertisedNamesAndValues = new Set([ + ...Object.keys(properties), + ...Object.values(properties).flatMap((property) => + typeof property === "object" && property !== null && Array.isArray(property.enum) + ? property.enum + : [], + ), + ]); + expect(documentedInputs.filter((name) => !advertisedNamesAndValues.has(name))).toEqual([]); + } finally { + await client.close(); + await server.close(); + } + }); +}); + describe("browser MCP tools", () => { const logger = createTestLogger(); diff --git a/skills/paseo/SKILL.md b/skills/paseo/SKILL.md index 11b5f2ff97..b7fe95811d 100644 --- a/skills/paseo/SKILL.md +++ b/skills/paseo/SKILL.md @@ -21,7 +21,7 @@ For a local daemon, `project create` defaults to the current directory and resol ## Workspaces -**`create_workspace`** — create a workspace independently of any agent. Required: `isolation` (`local` or `worktree`). Worktree isolation supports `mode: "branch-off" | "checkout-branch" | "checkout-pr"`: use `branchName`/`baseBranch` for a new branch, `branch` for an existing branch, or `prNumber` plus optional `forge`/`projectPath` for a change request. `worktreeSlug` controls the managed path. Returns the workspace descriptor centered on `workspaceId`. +**`create_workspace`** — create a workspace independently of any agent. Required: `isolation` (`local` or `worktree`). Worktree isolation supports `mode: "branch-off" | "checkout-branch" | "checkout-pr"`: use `branchName`/`baseBranch` for a new branch, `branch` for an existing branch, or `prNumber` plus optional `forge` for a change request. Use `path` for a local directory or source checkout and `projectId` for the owning project; when no path is supplied, a worktree may resolve its source from `projectId`. `worktreeSlug` controls the managed path. Returns the workspace descriptor centered on `workspaceId`. `background: true` hides a workspace from default lists and the sidebar while its contents remain durable and accessible; omit `background` to inherit your current workspace’s value (false without a caller), and override it only when the user explicitly asks.