@ai-sdk/harness-acp 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/harness-acp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -27,8 +27,8 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "ws": "^8.21.0",
30
- "@ai-sdk/harness": "1.0.63",
31
- "@ai-sdk/provider-utils": "5.0.24"
30
+ "@ai-sdk/harness": "1.0.65",
31
+ "@ai-sdk/provider-utils": "5.0.26"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "zod": "^3.25.76 || ^4.1.8"
@@ -1,19 +1,12 @@
1
1
  import type { HarnessV1 } from '@ai-sdk/harness';
2
2
  import type { ToolSet } from '@ai-sdk/provider-utils';
3
3
  import { z } from 'zod/v4';
4
- import {
5
- createACPAuthenticationProfileIdentity,
6
- resolveACPProviderAuthenticationCompatibility,
7
- type ACPAuthenticationProfileIdentity,
8
- type ACPClientApp,
9
- } from './acp-auth';
4
+ import type { ACPClientApp } from './acp-auth';
5
+ import type { ACPToolCall } from './acp-tool-call';
10
6
  import { createACPV1, type ACPV1Settings } from './v1';
11
- import { createImplementationIdentity } from './v1/implementation';
12
7
  import {
13
8
  acpColdSessionStateSchema,
14
9
  acpTurnStartConfigSchema,
15
- type ACPColdSessionState,
16
- type ACPTurnStartConfig,
17
10
  } from './v1/acp-v1-bridge-protocol';
18
11
  import { VERSION } from './version';
19
12
 
@@ -24,18 +17,29 @@ const ACP_CLIENT_APP = {
24
17
 
25
18
  export type ACPHarnessSettings<TBuiltinTools extends ToolSet = {}> = {
26
19
  readonly builtinTools?: TBuiltinTools;
20
+ /**
21
+ * MCP server definitions keyed by server name. Each definition uses the
22
+ * underlying runtime's native MCP server configuration format.
23
+ */
24
+ readonly mcpServers?: Record<string, unknown>;
25
+ readonly isMcpToolCall?: (toolCall: ACPToolCall) => boolean;
27
26
  readonly port?: number;
28
27
  readonly startupTimeoutMs?: number;
29
28
  readonly clientApp?: ACPClientApp;
30
29
  readonly version?: ACPV1Settings['version'];
31
30
  readonly harnessId: ACPV1Settings['harnessId'];
32
31
  readonly auth?: ACPV1Settings['auth'];
33
- readonly implementation: ACPV1Settings['implementation'];
32
+ readonly source: ACPV1Settings['source'];
33
+ readonly executable: ACPV1Settings['executable'];
34
+ readonly args?: ACPV1Settings['args'];
35
+ readonly forwardEnv?: ACPV1Settings['forwardEnv'];
36
+ readonly env?: ACPV1Settings['env'];
34
37
  readonly authentication?: ACPV1Settings['authentication'];
35
38
  readonly providerAuthentication?: ACPV1Settings['providerAuthentication'];
36
39
  readonly modelId?: ACPV1Settings['modelId'];
37
40
  readonly permissionModeMapping?: ACPV1Settings['permissionModeMapping'];
38
41
  readonly session?: ACPV1Settings['session'];
42
+ readonly mintBridgeToken?: ACPV1Settings['mintBridgeToken'];
39
43
  };
40
44
 
41
45
  const ACP_BUILTIN_TOOLS = {} as const satisfies ToolSet;
@@ -86,59 +90,24 @@ const acpResumeStateSchema = z.object({
86
90
  skillsFingerprint: z.string().optional(),
87
91
  });
88
92
 
89
- type ACPBridgeCoords = z.infer<typeof acpBridgeCoordsSchema>;
90
-
91
93
  export function createACP<TBuiltinTools extends ToolSet = {}>(
92
94
  settings: ACPHarnessSettings<TBuiltinTools>,
93
95
  ): HarnessV1<TBuiltinTools> {
96
+ if (
97
+ settings.mcpServers != null &&
98
+ Object.prototype.hasOwnProperty.call(
99
+ settings.mcpServers,
100
+ 'ai-sdk-harness-tools',
101
+ )
102
+ ) {
103
+ throw new Error(
104
+ 'ACP MCP server name "ai-sdk-harness-tools" is reserved for HarnessAgent tools.',
105
+ );
106
+ }
94
107
  const version = (settings as { readonly version?: string }).version ?? 'v1';
95
108
  switch (version) {
96
109
  case 'v1': {
97
110
  const clientApp = settings.clientApp ?? ACP_CLIENT_APP;
98
- const providerAuthenticationCompatibility =
99
- resolveACPProviderAuthenticationCompatibility({
100
- auth: settings.auth,
101
- providerAuthentication: settings.providerAuthentication,
102
- env: process.env,
103
- });
104
- const implementationIdentity = createImplementationIdentity({
105
- harnessId: settings.harnessId,
106
- acpVersion: version,
107
- implementation: settings.implementation,
108
- clientApp,
109
- providerAuthentication: providerAuthenticationCompatibility,
110
- permissionModeMapping: settings.permissionModeMapping,
111
- });
112
- const authenticationProfile = createACPAuthenticationProfileIdentity({
113
- authentication: settings.authentication,
114
- providerAuthenticationCompatibility,
115
- });
116
- const lifecycleStateSchema: z.ZodType<{
117
- readonly implementationIdentity: string;
118
- readonly authenticationProfile?: ACPAuthenticationProfileIdentity;
119
- readonly acpSessionId?: string;
120
- readonly bridge?: ACPBridgeCoords;
121
- readonly coldSession?: ACPColdSessionState;
122
- readonly turnStartConfig?: ACPTurnStartConfig;
123
- readonly recovery?: {
124
- readonly mode: 'disk-replay' | 'lossy-rerun';
125
- readonly reason: string;
126
- };
127
- readonly restoration?: {
128
- readonly method: 'resume' | 'load';
129
- };
130
- readonly initialGuidanceApplied?: boolean;
131
- readonly skillsMaterialized?: boolean;
132
- readonly skillsFingerprint?: string;
133
- }> = acpResumeStateSchema.extend({
134
- implementationIdentity: z.literal(implementationIdentity),
135
- authenticationProfile: acpResumeStateSchema.shape.authenticationProfile
136
- .unwrap()
137
- .extend({
138
- digest: z.literal(authenticationProfile.digest),
139
- })
140
- .optional(),
141
- });
142
111
  return createACPV1({
143
112
  settings,
144
113
  builtinTools:
@@ -146,10 +115,7 @@ export function createACP<TBuiltinTools extends ToolSet = {}>(
146
115
  port: settings.port,
147
116
  startupTimeoutMs: settings.startupTimeoutMs,
148
117
  clientApp,
149
- implementationIdentity,
150
- authenticationProfile,
151
- providerAuthenticationCompatibility,
152
- lifecycleStateSchema,
118
+ lifecycleStateSchema: acpResumeStateSchema,
153
119
  });
154
120
  }
155
121
  default:
@@ -0,0 +1,124 @@
1
+ export type ACPMetadata = Record<string, unknown>;
2
+
3
+ export type ACPRole = 'assistant' | 'user';
4
+
5
+ export type ACPAnnotations = {
6
+ audience?: Array<ACPRole> | null;
7
+ lastModified?: string | null;
8
+ priority?: number | null;
9
+ _meta?: ACPMetadata | null;
10
+ };
11
+
12
+ export type ACPTextContent = {
13
+ annotations?: ACPAnnotations | null;
14
+ text: string;
15
+ _meta?: ACPMetadata | null;
16
+ };
17
+
18
+ export type ACPImageContent = {
19
+ annotations?: ACPAnnotations | null;
20
+ data: string;
21
+ mimeType: string;
22
+ uri?: string | null;
23
+ _meta?: ACPMetadata | null;
24
+ };
25
+
26
+ export type ACPAudioContent = {
27
+ annotations?: ACPAnnotations | null;
28
+ data: string;
29
+ mimeType: string;
30
+ _meta?: ACPMetadata | null;
31
+ };
32
+
33
+ export type ACPResourceLink = {
34
+ annotations?: ACPAnnotations | null;
35
+ description?: string | null;
36
+ mimeType?: string | null;
37
+ name: string;
38
+ size?: number | null;
39
+ title?: string | null;
40
+ uri: string;
41
+ _meta?: ACPMetadata | null;
42
+ };
43
+
44
+ export type ACPTextResourceContents = {
45
+ mimeType?: string | null;
46
+ text: string;
47
+ uri: string;
48
+ _meta?: ACPMetadata | null;
49
+ };
50
+
51
+ export type ACPBlobResourceContents = {
52
+ blob: string;
53
+ mimeType?: string | null;
54
+ uri: string;
55
+ _meta?: ACPMetadata | null;
56
+ };
57
+
58
+ export type ACPEmbeddedResource = {
59
+ annotations?: ACPAnnotations | null;
60
+ resource: ACPTextResourceContents | ACPBlobResourceContents;
61
+ _meta?: ACPMetadata | null;
62
+ };
63
+
64
+ export type ACPContentBlock =
65
+ | (ACPTextContent & { type: 'text' })
66
+ | (ACPImageContent & { type: 'image' })
67
+ | (ACPAudioContent & { type: 'audio' })
68
+ | (ACPResourceLink & { type: 'resource_link' })
69
+ | (ACPEmbeddedResource & { type: 'resource' });
70
+
71
+ export type ACPToolCallContent =
72
+ | {
73
+ type: 'content';
74
+ content: ACPContentBlock;
75
+ _meta?: ACPMetadata | null;
76
+ }
77
+ | {
78
+ type: 'diff';
79
+ path: string;
80
+ oldText?: string | null;
81
+ newText: string;
82
+ _meta?: ACPMetadata | null;
83
+ }
84
+ | {
85
+ type: 'terminal';
86
+ terminalId: string;
87
+ _meta?: ACPMetadata | null;
88
+ };
89
+
90
+ export type ACPToolKind =
91
+ | 'read'
92
+ | 'edit'
93
+ | 'delete'
94
+ | 'move'
95
+ | 'search'
96
+ | 'execute'
97
+ | 'think'
98
+ | 'fetch'
99
+ | 'switch_mode'
100
+ | 'other';
101
+
102
+ export type ACPToolCallStatus =
103
+ | 'pending'
104
+ | 'in_progress'
105
+ | 'completed'
106
+ | 'failed';
107
+
108
+ export type ACPToolCallLocation = {
109
+ path: string;
110
+ line?: number | null;
111
+ _meta?: ACPMetadata | null;
112
+ };
113
+
114
+ export type ACPToolCall = {
115
+ toolCallId: string;
116
+ title: string;
117
+ kind?: ACPToolKind;
118
+ status?: ACPToolCallStatus;
119
+ content?: Array<ACPToolCallContent>;
120
+ locations?: Array<ACPToolCallLocation>;
121
+ rawInput?: unknown;
122
+ rawOutput?: unknown;
123
+ _meta?: ACPMetadata | null;
124
+ };
package/src/index.ts CHANGED
@@ -1,10 +1,28 @@
1
1
  export { createACP } from './acp-harness';
2
2
  export type { ACPAuthOptions, ACPClientApp } from './acp-auth';
3
3
  export type { ACPHarnessSettings } from './acp-harness';
4
+ export type {
5
+ ACPAnnotations,
6
+ ACPAudioContent,
7
+ ACPBlobResourceContents,
8
+ ACPContentBlock,
9
+ ACPEmbeddedResource,
10
+ ACPImageContent,
11
+ ACPMetadata,
12
+ ACPResourceLink,
13
+ ACPRole,
14
+ ACPTextContent,
15
+ ACPTextResourceContents,
16
+ ACPToolCall,
17
+ ACPToolCallContent,
18
+ ACPToolCallLocation,
19
+ ACPToolCallStatus,
20
+ ACPToolKind,
21
+ } from './acp-tool-call';
4
22
  export type {
5
23
  ACPAuthentication,
6
- ACPLockedNpmImplementation,
7
- ACPNpmImplementation,
24
+ ACPNpmLockedSource,
25
+ ACPNpmSimpleSource,
8
26
  ACPPermissionModeMapping,
9
27
  ACPPermissionModeTarget,
10
28
  ACPProfileValue,
@@ -12,7 +30,7 @@ export type {
12
30
  ACPProviderAuthenticationMode,
13
31
  ACPSerializablePrimitive,
14
32
  ACPSerializableValue,
15
- ACPSimpleNpmImplementation,
33
+ ACPSource,
16
34
  ACPV1Settings,
17
35
  ACPValueSource,
18
36
  } from './v1';
@@ -11,6 +11,7 @@ import {
11
11
  type HarnessV1ToolSpec,
12
12
  } from '@ai-sdk/harness';
13
13
  import { z } from 'zod/v4';
14
+ import type { ACPToolCall } from '../acp-tool-call';
14
15
  import type { ACPPermissionModeMapping } from './acp-v1-settings';
15
16
  import { acpTextContentBlockSchema } from './acp-v1-prompt';
16
17
 
@@ -126,11 +127,26 @@ const coldRestoreSchema = z.object({
126
127
  acpSessionId: z.string(),
127
128
  });
128
129
 
129
- export const outboundMessageSchema = harnessV1BridgeOutboundMessageSchema;
130
+ const acpToolCallCandidateSchema = z.object({
131
+ type: z.literal('acp-tool-call-candidate'),
132
+ toolCall: z.custom<ACPToolCall>(
133
+ value =>
134
+ value != null &&
135
+ typeof value === 'object' &&
136
+ typeof (value as { toolCallId?: unknown }).toolCallId === 'string' &&
137
+ typeof (value as { title?: unknown }).title === 'string',
138
+ ),
139
+ });
140
+
141
+ export const outboundMessageSchema = z.union([
142
+ harnessV1BridgeOutboundMessageSchema,
143
+ acpToolCallCandidateSchema,
144
+ ]);
130
145
  export type OutboundMessage = z.infer<typeof outboundMessageSchema>;
131
146
 
132
147
  export const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
133
148
  prompt: z.array(acpTextContentBlockSchema),
149
+ mcpServers: z.record(z.string(), z.unknown()).optional(),
134
150
  tools: z.array(acpSerializableToolSpecSchema).optional(),
135
151
  builtinTools: z.array(builtinToolSchema).readonly().default([]),
136
152
  permissionModeMapping: permissionModeMappingSchema.optional(),
@@ -34,13 +34,17 @@ import {
34
34
  } from '@ai-sdk/provider-utils';
35
35
  import { WebSocket } from 'ws';
36
36
  import {
37
+ createACPAuthenticationProfileIdentity,
37
38
  resolveACPProviderAuthentication,
39
+ resolveACPProviderAuthenticationCompatibility,
38
40
  type ACPAuthenticationProfileIdentity,
39
41
  type ACPClientApp,
40
- type ACPProviderAuthenticationCompatibility,
41
42
  } from '../acp-auth';
43
+ import type { ACPToolCall } from '../acp-tool-call';
42
44
  import {
45
+ createACPV1Implementation,
43
46
  createImplementationDescriptor,
47
+ createImplementationIdentity,
44
48
  createImplementationInstallCommand,
45
49
  createImplementationManifest,
46
50
  getImplementationLockfile,
@@ -112,9 +116,6 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
112
116
  port: portOverride,
113
117
  startupTimeoutMs,
114
118
  clientApp,
115
- implementationIdentity,
116
- authenticationProfile,
117
- providerAuthenticationCompatibility,
118
119
  lifecycleStateSchema,
119
120
  }: {
120
121
  settings: ACPV1Settings;
@@ -122,21 +123,28 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
122
123
  port?: number;
123
124
  startupTimeoutMs?: number;
124
125
  clientApp: ACPClientApp;
125
- implementationIdentity: string;
126
- authenticationProfile: ACPAuthenticationProfileIdentity;
127
- providerAuthenticationCompatibility:
128
- | ACPProviderAuthenticationCompatibility
129
- | undefined;
130
126
  lifecycleStateSchema: NonNullable<
131
127
  HarnessV1<TBuiltinTools>['lifecycleStateSchema']
132
128
  >;
133
129
  }): HarnessV1<TBuiltinTools> {
130
+ if (
131
+ settings.mcpServers != null &&
132
+ Object.prototype.hasOwnProperty.call(
133
+ settings.mcpServers,
134
+ 'ai-sdk-harness-tools',
135
+ )
136
+ ) {
137
+ throw new Error(
138
+ 'ACP MCP server name "ai-sdk-harness-tools" is reserved for HarnessAgent tools.',
139
+ );
140
+ }
134
141
  if (!HARNESS_ID_REGEXP.test(settings.harnessId)) {
135
142
  throw new Error(
136
143
  `ACP harnessId must be a stable kebab-case identifier; received ${JSON.stringify(settings.harnessId)}.`,
137
144
  );
138
145
  }
139
- validateACPV1Implementation(settings.implementation);
146
+ const implementation = createACPV1Implementation({ settings });
147
+ validateACPV1Implementation(implementation);
140
148
 
141
149
  const BOOTSTRAP_DIR = `.harness-bootstrap/${settings.harnessId}`;
142
150
 
@@ -164,7 +172,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
164
172
  readBridgeAsset({ name: 'host-tool-mcp.mjs' }),
165
173
  ]);
166
174
  const implementationLock = getImplementationLockfile({
167
- implementation: settings.implementation,
175
+ implementation,
168
176
  });
169
177
  cachedBootstrap = {
170
178
  harnessId: settings.harnessId,
@@ -186,14 +194,13 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
186
194
  {
187
195
  path: `${BOOTSTRAP_DIR}/implementation/package.json`,
188
196
  content: createImplementationManifest({
189
- implementation: settings.implementation,
197
+ implementation,
190
198
  }),
191
199
  },
192
200
  {
193
201
  path: `${BOOTSTRAP_DIR}/implementation/implementation.json`,
194
202
  content: createImplementationDescriptor({
195
- implementation: settings.implementation,
196
- implementationIdentity,
203
+ implementation,
197
204
  }),
198
205
  },
199
206
  ...(implementationLock == null
@@ -213,7 +220,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
213
220
  command: createImplementationInstallCommand({
214
221
  implementationDir: 'implementation',
215
222
  storeDir: '../.pnpm-store',
216
- implementation: settings.implementation,
223
+ implementation,
217
224
  }),
218
225
  },
219
226
  ],
@@ -229,6 +236,25 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
229
236
  });
230
237
  }
231
238
  const permissionMode = startOptions.permissionMode ?? 'allow-all';
239
+ const env = { ...process.env };
240
+ const providerAuthenticationCompatibility =
241
+ resolveACPProviderAuthenticationCompatibility({
242
+ auth: settings.auth,
243
+ providerAuthentication: settings.providerAuthentication,
244
+ env,
245
+ });
246
+ const implementationIdentity = createImplementationIdentity({
247
+ harnessId: settings.harnessId,
248
+ acpVersion: 'v1',
249
+ implementation,
250
+ clientApp,
251
+ providerAuthentication: providerAuthenticationCompatibility,
252
+ permissionModeMapping: settings.permissionModeMapping,
253
+ });
254
+ const authenticationProfile = createACPAuthenticationProfileIdentity({
255
+ authentication: settings.authentication,
256
+ providerAuthenticationCompatibility,
257
+ });
232
258
 
233
259
  const resolvedProviderAuthentication = resolveACPProviderAuthentication({
234
260
  auth: {
@@ -236,7 +262,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
236
262
  providerAuthentication: settings.providerAuthentication,
237
263
  clientApp,
238
264
  },
239
- env: process.env,
265
+ env,
240
266
  compatibility: providerAuthenticationCompatibility,
241
267
  });
242
268
  const sandboxSession = startOptions.sandboxSession;
@@ -356,6 +382,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
356
382
  builtinTools: builtinToolCatalog,
357
383
  permissionMode,
358
384
  permissionModeMapping,
385
+ mcpServers: settings.mcpServers,
386
+ isMcpToolCall: settings.isMcpToolCall,
359
387
  initialGuidanceApplied: resolveACPInitialGuidanceApplied({
360
388
  isResume: true,
361
389
  lifecycleState: lifecycleData,
@@ -407,6 +435,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
407
435
  sessionMeta: settings.session?.meta,
408
436
  builtinTools: builtinToolCatalog,
409
437
  permissionModeMapping,
438
+ mcpServers: settings.mcpServers,
410
439
  });
411
440
  respawnStrategy = {
412
441
  mode: 'lossy-rerun',
@@ -436,6 +465,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
436
465
  sessionMeta: settings.session?.meta,
437
466
  builtinTools: builtinToolCatalog,
438
467
  permissionModeMapping,
468
+ mcpServers: settings.mcpServers,
439
469
  debug: startOptions.observability?.debug,
440
470
  });
441
471
  respawnStrategy = {
@@ -451,7 +481,10 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
451
481
  override: portOverride,
452
482
  harnessId: settings.harnessId,
453
483
  });
454
- const token = randomBytes(32).toString('hex');
484
+ const token =
485
+ settings.mintBridgeToken == null
486
+ ? randomBytes(32).toString('hex')
487
+ : settings.mintBridgeToken(sandboxSession.id);
455
488
  await sandbox.run({
456
489
  command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,
457
490
  abortSignal: startOptions.abortSignal,
@@ -472,8 +505,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
472
505
  ` --bridge-type ${shellQuote(settings.harnessId)}`,
473
506
  env: {
474
507
  ...resolveImplementationEnvironment({
475
- implementation: settings.implementation,
476
- env: process.env,
508
+ implementation,
509
+ env,
477
510
  }),
478
511
  ...createACPBridgeEnvironment({
479
512
  authentication: settings.authentication,
@@ -556,6 +589,9 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
556
589
  builtinTools: builtinToolCatalog,
557
590
  permissionMode,
558
591
  permissionModeMapping,
592
+ ...(settings.mcpServers == null
593
+ ? {}
594
+ : { mcpServers: settings.mcpServers }),
559
595
  tools: respawnStrategy.turnStartConfig.tools,
560
596
  turnStartConfig: respawnStrategy.turnStartConfig,
561
597
  recoveryMode: {
@@ -592,6 +628,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
592
628
  builtinTools: builtinToolCatalog,
593
629
  permissionMode,
594
630
  permissionModeMapping,
631
+ mcpServers: settings.mcpServers,
632
+ isMcpToolCall: settings.isMcpToolCall,
595
633
  initialGuidanceApplied: resolveACPInitialGuidanceApplied({
596
634
  isResume,
597
635
  lifecycleState: lifecycleData,
@@ -793,6 +831,8 @@ function createSession({
793
831
  builtinTools,
794
832
  permissionMode,
795
833
  permissionModeMapping,
834
+ mcpServers,
835
+ isMcpToolCall,
796
836
  initialGuidanceApplied: initialGuidanceAppliedAtStart,
797
837
  skillCatalog,
798
838
  skillsFingerprint,
@@ -821,6 +861,8 @@ function createSession({
821
861
  builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
822
862
  permissionMode: NonNullable<StartMessage['permissionMode']>;
823
863
  permissionModeMapping: StartMessage['permissionModeMapping'];
864
+ mcpServers: Record<string, unknown> | undefined;
865
+ isMcpToolCall: ((toolCall: ACPToolCall) => boolean) | undefined;
824
866
  initialGuidanceApplied: boolean;
825
867
  skillCatalog: ReadonlyArray<ACPSkillCatalogEntry>;
826
868
  skillsFingerprint: string;
@@ -875,6 +917,8 @@ function createSession({
875
917
  let openBlock:
876
918
  | { readonly type: 'text' | 'reasoning'; readonly id: string }
877
919
  | undefined;
920
+ const dynamicToolCalls = new Map<string, boolean>();
921
+ const toolCallClassificationErrors = new Map<string, unknown>();
878
922
  const subscriptions: Array<() => void> = [];
879
923
  const forward = (event: HarnessV1StreamPart) => {
880
924
  if (event.type === 'text-start' || event.type === 'reasoning-start') {
@@ -909,9 +953,7 @@ function createSession({
909
953
  'reasoning-start',
910
954
  'reasoning-delta',
911
955
  'reasoning-end',
912
- 'tool-call',
913
956
  'tool-approval-request',
914
- 'tool-result',
915
957
  'file-change',
916
958
  'finish-step',
917
959
  'raw',
@@ -940,6 +982,45 @@ function createSession({
940
982
  settle({ error: abortError });
941
983
  }
942
984
  };
985
+ subscriptions.push(
986
+ channel.on('acp-tool-call-candidate', event => {
987
+ try {
988
+ dynamicToolCalls.set(
989
+ event.toolCall.toolCallId,
990
+ isMcpToolCall?.(event.toolCall) === true,
991
+ );
992
+ } catch (error) {
993
+ toolCallClassificationErrors.set(event.toolCall.toolCallId, error);
994
+ }
995
+ }),
996
+ );
997
+ subscriptions.push(
998
+ channel.on('tool-call', event => {
999
+ if (toolCallClassificationErrors.has(event.toolCallId)) {
1000
+ const error = toolCallClassificationErrors.get(event.toolCallId);
1001
+ closeForwardedBlock();
1002
+ forward({ type: 'error', error });
1003
+ try {
1004
+ channel.send({ type: 'abort' });
1005
+ } catch {}
1006
+ settle({ error });
1007
+ return;
1008
+ }
1009
+ forward(
1010
+ dynamicToolCalls.get(event.toolCallId) === true
1011
+ ? { ...event, dynamic: true }
1012
+ : event,
1013
+ );
1014
+ }),
1015
+ );
1016
+ subscriptions.push(
1017
+ channel.on('tool-result', event => {
1018
+ const dynamic = dynamicToolCalls.get(event.toolCallId) === true;
1019
+ dynamicToolCalls.delete(event.toolCallId);
1020
+ toolCallClassificationErrors.delete(event.toolCallId);
1021
+ forward(dynamic ? { ...event, dynamic: true } : event);
1022
+ }),
1023
+ );
943
1024
  for (const type of eventTypes) {
944
1025
  subscriptions.push(channel.on(type, event => forward(event)));
945
1026
  }
@@ -1096,6 +1177,7 @@ function createSession({
1096
1177
  builtinTools,
1097
1178
  permissionMode,
1098
1179
  permissionModeMapping,
1180
+ mcpServers,
1099
1181
  debug,
1100
1182
  authenticationProfile,
1101
1183
  sessionMeta,
@@ -1123,6 +1205,7 @@ function createSession({
1123
1205
  builtinTools,
1124
1206
  permissionMode,
1125
1207
  permissionModeMapping,
1208
+ ...(mcpServers == null ? {} : { mcpServers }),
1126
1209
  tools: options.tools == null ? undefined : turnStartConfig.tools,
1127
1210
  turnStartConfig,
1128
1211
  });
@@ -1160,6 +1243,7 @@ function createSession({
1160
1243
  builtinTools: turnStartConfig.builtinTools,
1161
1244
  permissionMode: turnStartConfig.permissionMode,
1162
1245
  permissionModeMapping: turnStartConfig.permissionModeMapping,
1246
+ ...(mcpServers == null ? {} : { mcpServers }),
1163
1247
  tools: turnStartConfig.tools,
1164
1248
  turnStartConfig,
1165
1249
  recoveryMode: {
@@ -1324,12 +1408,14 @@ function validateACPTurnStartConfig({
1324
1408
  sessionMeta,
1325
1409
  builtinTools,
1326
1410
  permissionModeMapping,
1411
+ mcpServers,
1327
1412
  }: {
1328
1413
  turnStartConfig: ACPTurnStartConfig;
1329
1414
  authenticationProfile: ACPAuthenticationProfileIdentity;
1330
1415
  sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
1331
1416
  builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
1332
1417
  permissionModeMapping: ACPPermissionModeMapping | undefined;
1418
+ mcpServers: Record<string, unknown> | undefined;
1333
1419
  }): void {
1334
1420
  const current = createACPTurnStartConfig({
1335
1421
  prompt: turnStartConfig.prompt,
@@ -1337,6 +1423,7 @@ function validateACPTurnStartConfig({
1337
1423
  builtinTools,
1338
1424
  permissionMode: turnStartConfig.permissionMode,
1339
1425
  permissionModeMapping,
1426
+ mcpServers,
1340
1427
  debug: turnStartConfig.debug,
1341
1428
  authenticationProfile,
1342
1429
  sessionMeta,
@@ -1359,6 +1446,7 @@ function validateACPColdSessionConfiguration({
1359
1446
  sessionMeta,
1360
1447
  builtinTools,
1361
1448
  permissionModeMapping,
1449
+ mcpServers,
1362
1450
  debug,
1363
1451
  }: {
1364
1452
  coldSession: ACPColdSessionState;
@@ -1368,6 +1456,7 @@ function validateACPColdSessionConfiguration({
1368
1456
  sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
1369
1457
  builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
1370
1458
  permissionModeMapping: ACPPermissionModeMapping | undefined;
1459
+ mcpServers: Record<string, unknown> | undefined;
1371
1460
  debug: HarnessV1DebugConfig | undefined;
1372
1461
  }): ACPTurnStartConfig {
1373
1462
  const current = createACPTurnStartConfig({
@@ -1376,6 +1465,7 @@ function validateACPColdSessionConfiguration({
1376
1465
  builtinTools,
1377
1466
  permissionMode,
1378
1467
  permissionModeMapping,
1468
+ mcpServers,
1379
1469
  debug,
1380
1470
  authenticationProfile,
1381
1471
  sessionMeta,