diff --git a/packages/server/src/server/agent/mcp-parity.e2e.test.ts b/packages/server/src/server/agent/mcp-parity.e2e.test.ts index b5b0bfa41..7370e6082 100644 --- a/packages/server/src/server/agent/mcp-parity.e2e.test.ts +++ b/packages/server/src/server/agent/mcp-parity.e2e.test.ts @@ -390,6 +390,26 @@ describe("Suite A: Core Fixes", () => { } }); + test("list_provider_features returns draft provider features over MCP", async () => { + const payload = await callToolStructured(topLevelClient, "list_provider_features", { + provider: "claude", + cwd: parentAgentCwd, + model: "claude-test-model", + featureValues: { test_feature: true }, + }); + + expect(payload.provider).toBe("claude"); + expect(recordArr(payload.features)).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: "toggle", + id: "test_feature", + value: true, + }), + ]), + ); + }); + test("create_agent accepts labels param", async () => { let agentId: string | null = null; try { diff --git a/packages/server/src/server/agent/mcp-server.test.ts b/packages/server/src/server/agent/mcp-server.test.ts index 65cb49fa6..f857143f2 100644 --- a/packages/server/src/server/agent/mcp-server.test.ts +++ b/packages/server/src/server/agent/mcp-server.test.ts @@ -133,6 +133,7 @@ function buildAgentManagerSpies() { cancelAgentRun: vi.fn(), getPendingPermissions: vi.fn(), getRegisteredProviderIds: vi.fn().mockReturnValue(["claude"]), + listDraftFeatures: vi.fn(), }; } @@ -1793,6 +1794,57 @@ describe("provider listing MCP tool", () => { describe("model listing MCP tool", () => { const logger = createTestLogger(); + it("lists provider features for a draft agent configuration", async () => { + const { agentManager, agentStorage, spies } = createTestDeps(); + spies.agentManager.listDraftFeatures.mockResolvedValue([ + { + type: "toggle", + id: "fast_mode", + label: "Fast mode", + value: false, + }, + ]); + const server = await createAgentMcpServer({ + agentManager, + agentStorage, + logger, + }); + const tool = registeredTool(server, "list_provider_features"); + const input = { + provider: "codex", + cwd: "~/repo", + modeId: "full-access", + model: "gpt-5.4", + thinkingOptionId: "high", + featureValues: { fast_mode: true }, + }; + + const parsed = await tool.inputSchema.safeParseAsync(input); + expect(parsed.success).toBe(true); + + const response = await tool.handler(input); + + expect(spies.agentManager.listDraftFeatures).toHaveBeenCalledWith({ + provider: "codex", + cwd: expect.stringContaining("repo"), + modeId: "full-access", + model: "gpt-5.4", + thinkingOptionId: "high", + featureValues: { fast_mode: true }, + }); + expect(response.structuredContent).toEqual({ + provider: "codex", + features: [ + { + type: "toggle", + id: "fast_mode", + label: "Fast mode", + value: false, + }, + ], + }); + }); + it("rejects disabled providers without fetching models", async () => { const { agentManager, agentStorage } = createTestDeps(); const fetchModels = vi.fn().mockResolvedValue([ diff --git a/packages/server/src/server/agent/mcp-server.ts b/packages/server/src/server/agent/mcp-server.ts index b547c860d..28a55a02c 100644 --- a/packages/server/src/server/agent/mcp-server.ts +++ b/packages/server/src/server/agent/mcp-server.ts @@ -8,6 +8,7 @@ import type { ServerNotification, ServerRequest } from "@modelcontextprotocol/sd import type { AgentProvider } from "./agent-sdk-types.js"; import type { AgentManager, WaitForAgentResult } from "./agent-manager.js"; import { + AgentFeatureSchema, AgentPermissionRequestPayloadSchema, AgentListItemPayloadSchema, AgentPermissionResponseSchema, @@ -587,6 +588,14 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom const createAgentInputSchema = callerAgentId ? agentToAgentInputSchema : topLevelInputSchema; const agentToAgentCreateAgentArgsSchema = z.object(agentToAgentInputSchema).strict(); const topLevelCreateAgentArgsSchema = z.object(topLevelInputSchema).strict(); + const listProviderFeaturesInputSchema = { + provider: AgentProviderEnum, + cwd: z.string().describe("Working directory used to resolve provider feature availability."), + modeId: z.string().optional(), + model: z.string().optional(), + thinkingOptionId: z.string().optional(), + featureValues: z.record(z.unknown()).optional(), + }; if (options.voiceOnly || options.enableVoiceTools || callerContext?.enableVoiceTools) { server.registerTool( @@ -1813,6 +1822,37 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom }, ); + server.registerTool( + "list_provider_features", + { + title: "List provider features", + description: + "List provider-specific features available for a draft agent configuration, such as Codex fast_mode.", + inputSchema: listProviderFeaturesInputSchema, + outputSchema: { + provider: AgentProviderEnum, + features: z.array(AgentFeatureSchema), + }, + }, + async ({ provider, cwd, modeId, model, thinkingOptionId, featureValues }) => { + const features = await agentManager.listDraftFeatures({ + provider, + cwd: expandUserPath(cwd), + ...(modeId ? { modeId } : {}), + ...(model ? { model } : {}), + ...(thinkingOptionId ? { thinkingOptionId } : {}), + ...(featureValues ? { featureValues } : {}), + }); + return { + content: [], + structuredContent: ensureValidJson({ + provider, + features, + }), + }; + }, + ); + server.registerTool( "list_worktrees", { diff --git a/public-docs/mcp.md b/public-docs/mcp.md index 66da4520f..29d747329 100644 --- a/public-docs/mcp.md +++ b/public-docs/mcp.md @@ -53,10 +53,11 @@ The MCP server itself is controlled by `daemon.mcp.enabled`. Existing agents may ### Providers -| Tool | Function | -| ---------------- | --------------------------------------------------------- | -| `list_providers` | List configured agent providers, availability, and modes. | -| `list_models` | List models for an agent provider. | +| Tool | Function | +| ------------------------ | ------------------------------------------------------------------------------------------- | +| `list_providers` | List configured agent providers, availability, and modes. | +| `list_models` | List models for an agent provider. | +| `list_provider_features` | List provider-specific features for a draft agent configuration, such as Codex `fast_mode`. | ### Worktrees diff --git a/skills/paseo/SKILL.md b/skills/paseo/SKILL.md index 47bba1539..1115202a9 100644 --- a/skills/paseo/SKILL.md +++ b/skills/paseo/SKILL.md @@ -34,6 +34,12 @@ Compose: call `create_worktree` first, then `create_agent` with `cwd` set to the **`archive_agent`** — `{ agentId }`. Interrupts if running, removes from active list. +## Provider features + +**`list_provider_features`** — query provider-specific features before setting them. Required: `provider`, `cwd`. Optional: `model`, `modeId`, `thinkingOptionId`, `featureValues`. + +Only set feature IDs returned by `list_provider_features`. For Codex fast mode, look for `fast_mode` and pass `features: { "fast_mode": true }` to `create_agent`. + ## Heartbeats **`create_schedule`** — required: `prompt`. Pick one of `cron` or `every` (`"5m"`, `"1h"`). Optional: `name`, `target` (`self` | `new-agent`), `provider`, `maxRuns`, `expiresIn`. Use for periodic checks on long-running work or recurring maintenance.