diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts index e23148973e..7f89ad9ead 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts @@ -497,6 +497,74 @@ describe("ClaudeAdapterLive", () => { ); }); + it.effect("forwards mcpServers from providerOptions to query options", () => { + const harness = makeHarness(); + return Effect.gen(function* () { + const adapter = yield* ClaudeAdapter; + const mcpServers = { + filesystem: { + command: "npx", + args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"], + }, + }; + yield* adapter.startSession({ + threadId: THREAD_ID, + provider: "claudeAgent", + runtimeMode: "full-access", + providerOptions: { + claudeAgent: { mcpServers }, + }, + }); + + const createInput = harness.getLastCreateQueryInput(); + assert.deepStrictEqual(createInput?.options.mcpServers, mcpServers); + }).pipe( + Effect.provideService(Random.Random, makeDeterministicRandomService()), + Effect.provide(harness.layer), + ); + }); + + it.effect("forwards plugins from providerOptions to query options", () => { + const harness = makeHarness(); + return Effect.gen(function* () { + const adapter = yield* ClaudeAdapter; + const plugins = [{ type: "local" as const, path: "./my-plugin" }]; + yield* adapter.startSession({ + threadId: THREAD_ID, + provider: "claudeAgent", + runtimeMode: "full-access", + providerOptions: { + claudeAgent: { plugins }, + }, + }); + + const createInput = harness.getLastCreateQueryInput(); + assert.deepStrictEqual(createInput?.options.plugins, plugins); + }).pipe( + Effect.provideService(Random.Random, makeDeterministicRandomService()), + Effect.provide(harness.layer), + ); + }); + + it.effect("omits mcpServers and plugins when not provided", () => { + const harness = makeHarness(); + return Effect.gen(function* () { + const adapter = yield* ClaudeAdapter; + yield* adapter.startSession({ + threadId: THREAD_ID, + provider: "claudeAgent", + runtimeMode: "full-access", + }); + + const createInput = harness.getLastCreateQueryInput(); + assert.equal(createInput?.options.mcpServers, undefined); + assert.equal(createInput?.options.plugins, undefined); + }).pipe( + Effect.provideService(Random.Random, makeDeterministicRandomService()), + Effect.provide(harness.layer), + ); + }); + it.effect("treats ultrathink as a prompt keyword instead of a session effort", () => { const harness = makeHarness(); return Effect.gen(function* () { diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index acbee86053..30b7c1e465 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -2585,6 +2585,12 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) { env: process.env, ...(input.cwd ? { additionalDirectories: [input.cwd] } : {}), }; + if (providerOptions?.mcpServers) { + (queryOptions as Record).mcpServers = providerOptions.mcpServers; + } + if (providerOptions?.plugins) { + (queryOptions as Record).plugins = providerOptions.plugins; + } const queryRuntime = yield* Effect.try({ try: () => diff --git a/packages/contracts/src/orchestration.ts b/packages/contracts/src/orchestration.ts index 3208adc8bb..b31a2cc8fe 100644 --- a/packages/contracts/src/orchestration.ts +++ b/packages/contracts/src/orchestration.ts @@ -53,6 +53,8 @@ export const ClaudeProviderStartOptions = Schema.Struct({ binaryPath: Schema.optional(TrimmedNonEmptyString), permissionMode: Schema.optional(TrimmedNonEmptyString), maxThinkingTokens: Schema.optional(NonNegativeInt), + mcpServers: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), + plugins: Schema.optional(Schema.Array(Schema.Unknown)), }); export const ProviderStartOptions = Schema.Struct({