@ai-sdk/harness-claude-code 1.0.66 → 1.0.68

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.
@@ -63,6 +63,11 @@ const CLAUDE_CODE_CLIENT_APP = `ai-sdk/harness-claude-code/${VERSION}`;
63
63
 
64
64
  export type ClaudeCodeHarnessSettings = {
65
65
  readonly auth?: ClaudeCodeAuthOptions;
66
+ /**
67
+ * MCP server definitions keyed by server name. Each definition uses the
68
+ * underlying runtime's native MCP server configuration format.
69
+ */
70
+ readonly mcpServers?: Record<string, unknown>;
66
71
  /**
67
72
  * Anthropic model id the underlying `claude` CLI should use. Leaving this
68
73
  * unset defers to the CLI's default.
@@ -92,19 +97,53 @@ export type ClaudeCodeHarnessSettings = {
92
97
  readonly port?: number;
93
98
  /** Maximum milliseconds to wait for the bridge to advertise its port. Defaults to 120000. */
94
99
  readonly startupTimeoutMs?: number;
100
+ /**
101
+ * Creates the authentication token used by the sandbox bridge. Defaults to
102
+ * a random 32-byte hexadecimal token.
103
+ */
104
+ readonly mintBridgeToken?: (sandboxId: string) => string;
95
105
  };
96
106
 
97
107
  /*
98
108
  * Every native tool the Claude Code CLI can invoke, declared as a `ToolSet`
99
109
  * keyed by what the bridge emits as `toolName` on the wire
100
- * (`commonName ?? nativeName`). Schemas transcribed from
101
- * `@anthropic-ai/claude-agent-sdk`'s `agentSdkTypes.d.ts`.
110
+ * (`commonName ?? nativeName`). Schemas are transcribed from the generated
111
+ * `sdk-tools.d.ts` and the conditionally registered tool definitions in the
112
+ * pinned Claude Code executable.
102
113
  *
103
114
  * `MCP` (the generic proxy tool inside the Claude Code SDK) is intentionally
104
115
  * omitted — the bridge filters out `mcp__harness-tools__*` tool names before
105
116
  * emitting them, and other MCP invocations come through with their own
106
117
  * server-tool names rather than the literal `'Mcp'` token.
107
118
  */
119
+ const listMcpResourcesInputSchema = z.object({
120
+ server: z.string().optional(),
121
+ });
122
+
123
+ const readMcpResourceInputSchema = z.object({
124
+ server: z.string(),
125
+ uri: z.string(),
126
+ });
127
+
128
+ const structuredTeamMessageSchema = z.discriminatedUnion('type', [
129
+ z.object({
130
+ type: z.literal('shutdown_request'),
131
+ reason: z.string().optional(),
132
+ }),
133
+ z.object({
134
+ type: z.literal('shutdown_response'),
135
+ request_id: z.string(),
136
+ approve: z.boolean(),
137
+ reason: z.string().optional(),
138
+ }),
139
+ z.object({
140
+ type: z.literal('plan_approval_response'),
141
+ request_id: z.string(),
142
+ approve: z.boolean(),
143
+ feedback: z.string().optional(),
144
+ }),
145
+ ]);
146
+
108
147
  const CLAUDE_CODE_BUILTIN_TOOLS = {
109
148
  read: commonTool('read', {
110
149
  nativeName: 'Read',
@@ -296,23 +335,66 @@ const CLAUDE_CODE_BUILTIN_TOOLS = {
296
335
  timeout: z.number(),
297
336
  }),
298
337
  }),
299
- Monitor: tool({
300
- description: 'Run and monitor a shell command',
301
- inputSchema: z.object({
302
- command: z.string(),
303
- description: z.string().optional(),
304
- timeout_ms: z.number().optional(),
305
- persistent: z.boolean().optional(),
338
+ Monitor: {
339
+ ...tool({
340
+ description: 'Run and monitor a shell command or WebSocket',
341
+ inputSchema: z.object({
342
+ description: z.string().optional(),
343
+ timeout_ms: z.number().optional(),
344
+ persistent: z.boolean().optional(),
345
+ command: z.string().optional(),
346
+ ws: z
347
+ .object({
348
+ url: z.string(),
349
+ protocols: z.array(z.string()).optional(),
350
+ })
351
+ .optional(),
352
+ }),
306
353
  }),
307
- }),
308
- ListMcpResources: tool({
309
- description: 'List resources available from MCP servers',
310
- inputSchema: z.object({ server: z.string().optional() }),
311
- }),
312
- ReadMcpResource: tool({
313
- description: 'Read a specific MCP resource by URI',
314
- inputSchema: z.object({ server: z.string(), uri: z.string() }),
315
- }),
354
+ toolUseKind: 'bash',
355
+ },
356
+ ListMcpResources: {
357
+ ...tool({
358
+ description: 'List resources available from MCP servers',
359
+ inputSchema: listMcpResourcesInputSchema,
360
+ }),
361
+ toolUseKind: 'readonly',
362
+ },
363
+ ListMcpResourcesTool: {
364
+ ...tool({
365
+ description: 'List resources available from MCP servers',
366
+ inputSchema: listMcpResourcesInputSchema,
367
+ }),
368
+ toolUseKind: 'readonly',
369
+ },
370
+ ReadMcpResource: {
371
+ ...tool({
372
+ description: 'Read a specific MCP resource by URI',
373
+ inputSchema: readMcpResourceInputSchema,
374
+ }),
375
+ toolUseKind: 'readonly',
376
+ },
377
+ ReadMcpResourceTool: {
378
+ ...tool({
379
+ description: 'Read a specific MCP resource by URI',
380
+ inputSchema: readMcpResourceInputSchema,
381
+ }),
382
+ toolUseKind: 'readonly',
383
+ },
384
+ ReadMcpResourceDirTool: {
385
+ ...tool({
386
+ description: 'List direct children of an MCP directory resource',
387
+ inputSchema: readMcpResourceInputSchema,
388
+ }),
389
+ toolUseKind: 'readonly',
390
+ },
391
+ RefreshMcpTools: {
392
+ ...tool({
393
+ description: 'Refresh tools from one or all connected MCP servers',
394
+ inputSchema: z.object({ server: z.string().optional() }),
395
+ }),
396
+ toolUseKind: 'readonly',
397
+ },
316
398
  ExitPlanMode: tool({
317
399
  description: 'Exit plan mode with optional permission approvals',
318
400
  inputSchema: z.looseObject({
@@ -326,6 +408,13 @@ const CLAUDE_CODE_BUILTIN_TOOLS = {
326
408
  .optional(),
327
409
  }),
328
410
  }),
411
+ EnterPlanMode: {
412
+ ...tool({
413
+ description: 'Enter plan mode',
414
+ inputSchema: z.object({}),
415
+ }),
416
+ toolUseKind: 'readonly',
417
+ },
329
418
  EnterWorktree: tool({
330
419
  description: 'Create or enter an isolated git worktree',
331
420
  inputSchema: z.object({
@@ -391,6 +480,271 @@ const CLAUDE_CODE_BUILTIN_TOOLS = {
391
480
  max_results: z.number().optional(),
392
481
  }),
393
482
  }),
483
+ Artifact: {
484
+ ...tool({
485
+ description: 'Publish or list claude.ai artifacts',
486
+ inputSchema: z.object({
487
+ action: z.enum(['publish', 'list']).optional(),
488
+ file_path: z.string().optional(),
489
+ favicon: z.string().optional(),
490
+ limit: z.number().optional(),
491
+ scope: z.enum(['mine', 'shared', 'all']).optional(),
492
+ title: z.string().optional(),
493
+ description: z.string().optional(),
494
+ label: z.string().optional(),
495
+ url: z.string().optional(),
496
+ force: z.boolean().optional(),
497
+ }),
498
+ }),
499
+ toolUseKind: 'edit',
500
+ },
501
+ CronCreate: {
502
+ ...tool({
503
+ description: 'Schedule a recurring or one-shot prompt',
504
+ inputSchema: z.object({
505
+ cron: z.string(),
506
+ prompt: z.string(),
507
+ recurring: z.boolean().optional(),
508
+ durable: z.boolean().optional(),
509
+ }),
510
+ }),
511
+ toolUseKind: 'edit',
512
+ },
513
+ CronDelete: {
514
+ ...tool({
515
+ description: 'Delete a scheduled prompt by id',
516
+ inputSchema: z.object({ id: z.string() }),
517
+ }),
518
+ toolUseKind: 'edit',
519
+ },
520
+ CronList: {
521
+ ...tool({
522
+ description: 'List scheduled prompts for the current session',
523
+ inputSchema: z.object({}),
524
+ }),
525
+ toolUseKind: 'readonly',
526
+ },
527
+ DesignSync: {
528
+ ...tool({
529
+ description: 'Read or update claude.ai Design projects',
530
+ inputSchema: z.object({
531
+ method: z.enum([
532
+ 'list_projects',
533
+ 'get_project',
534
+ 'list_files',
535
+ 'get_file',
536
+ 'finalize_plan',
537
+ 'write_files',
538
+ 'delete_files',
539
+ 'register_assets',
540
+ 'unregister_assets',
541
+ 'create_project',
542
+ 'report_validate',
543
+ ]),
544
+ projectId: z.string().optional(),
545
+ path: z.string().optional(),
546
+ writes: z.array(z.string()).max(256).optional(),
547
+ deletes: z.array(z.string()).max(256).optional(),
548
+ planId: z.string().optional(),
549
+ files: z
550
+ .array(
551
+ z.object({
552
+ path: z.string(),
553
+ localPath: z.string().optional(),
554
+ data: z.string().optional(),
555
+ encoding: z.literal('base64').optional(),
556
+ mimeType: z.string().optional(),
557
+ }),
558
+ )
559
+ .max(256)
560
+ .optional(),
561
+ paths: z.array(z.string()).max(256).optional(),
562
+ name: z.string().optional(),
563
+ assets: z
564
+ .array(
565
+ z.object({
566
+ name: z.string(),
567
+ path: z.string(),
568
+ subtitle: z.string().optional(),
569
+ viewport: z
570
+ .object({
571
+ width: z.number(),
572
+ height: z.number().optional(),
573
+ })
574
+ .optional(),
575
+ group: z.string().optional(),
576
+ }),
577
+ )
578
+ .max(256)
579
+ .optional(),
580
+ localDir: z.string().optional(),
581
+ counts: z
582
+ .object({
583
+ total: z.number(),
584
+ bad: z.number(),
585
+ thin: z.number(),
586
+ variantsIdentical: z.number(),
587
+ iterations: z.number(),
588
+ })
589
+ .optional(),
590
+ }),
591
+ }),
592
+ toolUseKind: 'edit',
593
+ },
594
+ LSP: {
595
+ ...tool({
596
+ description: 'Query a language server for code intelligence',
597
+ inputSchema: z.object({
598
+ operation: z.enum([
599
+ 'goToDefinition',
600
+ 'findReferences',
601
+ 'hover',
602
+ 'documentSymbol',
603
+ 'workspaceSymbol',
604
+ 'goToImplementation',
605
+ 'prepareCallHierarchy',
606
+ 'incomingCalls',
607
+ 'outgoingCalls',
608
+ ]),
609
+ filePath: z.string(),
610
+ line: z.number().int().positive(),
611
+ character: z.number().int().positive(),
612
+ query: z.string().optional(),
613
+ }),
614
+ }),
615
+ toolUseKind: 'readonly',
616
+ },
617
+ PowerShell: {
618
+ ...tool({
619
+ description: 'Execute a PowerShell command, optionally in background',
620
+ inputSchema: z.object({
621
+ command: z.string(),
622
+ timeout: z.number().optional(),
623
+ description: z.string().optional(),
624
+ run_in_background: z.boolean().optional(),
625
+ dangerouslyDisableSandbox: z.boolean().optional(),
626
+ }),
627
+ }),
628
+ toolUseKind: 'bash',
629
+ },
630
+ PushNotification: {
631
+ ...tool({
632
+ description: 'Send a notification for proactive or scheduled work',
633
+ inputSchema: z.object({
634
+ message: z.string(),
635
+ status: z.literal('proactive'),
636
+ }),
637
+ }),
638
+ toolUseKind: 'edit',
639
+ },
640
+ RemoteTrigger: {
641
+ ...tool({
642
+ description: 'List, manage, or run a claude.ai Routine trigger',
643
+ inputSchema: z.object({
644
+ action: z.enum(['list', 'get', 'create', 'update', 'run']),
645
+ trigger_id: z.string().optional(),
646
+ body: z.record(z.string(), z.unknown()).optional(),
647
+ }),
648
+ }),
649
+ toolUseKind: 'edit',
650
+ },
651
+ ReportFindings: {
652
+ ...tool({
653
+ description: 'Return verified code-review findings',
654
+ inputSchema: z.object({
655
+ level: z.enum(['low', 'medium', 'high', 'xhigh', 'max']).optional(),
656
+ findings: z
657
+ .array(
658
+ z.object({
659
+ file: z.string(),
660
+ line: z.number().optional(),
661
+ summary: z.string(),
662
+ short_summary: z.string().optional(),
663
+ failure_scenario: z.string(),
664
+ category: z.string().optional(),
665
+ verdict: z.enum(['CONFIRMED', 'PLAUSIBLE']).optional(),
666
+ outcome: z
667
+ .enum(['fixed', 'skipped', 'no_change_needed'])
668
+ .optional(),
669
+ }),
670
+ )
671
+ .max(32),
672
+ }),
673
+ }),
674
+ toolUseKind: 'readonly',
675
+ },
676
+ ScheduleWakeup: {
677
+ ...tool({
678
+ description: 'Schedule or stop the next iteration of a dynamic loop',
679
+ inputSchema: z.object({
680
+ delaySeconds: z.number().optional(),
681
+ reason: z.string().optional(),
682
+ prompt: z.string().optional(),
683
+ stop: z.boolean().optional(),
684
+ }),
685
+ }),
686
+ toolUseKind: 'edit',
687
+ },
688
+ SendMessage: {
689
+ ...tool({
690
+ description: 'Send a plain-text or protocol message to another agent',
691
+ inputSchema: z.object({
692
+ to: z.string(),
693
+ summary: z.string().max(200).optional(),
694
+ message: z.union([z.string(), structuredTeamMessageSchema]),
695
+ }),
696
+ }),
697
+ toolUseKind: 'edit',
698
+ },
699
+ SendUserFile: {
700
+ ...tool({
701
+ description: 'Send one or more files to the user',
702
+ inputSchema: z.object({
703
+ files: z.union([z.string(), z.array(z.string()).min(1)]),
704
+ caption: z.string().optional(),
705
+ status: z.enum(['normal', 'proactive']),
706
+ display: z.enum(['render', 'attach']).optional(),
707
+ }),
708
+ }),
709
+ toolUseKind: 'readonly',
710
+ },
711
+ ShareOnboardingGuide: {
712
+ ...tool({
713
+ description: 'Create, update, inspect, or delete an onboarding guide',
714
+ inputSchema: z.object({
715
+ mode: z.enum(['check', 'update', 'create', 'delete']).optional(),
716
+ short_code: z
717
+ .string()
718
+ .regex(/^[A-Za-z0-9_-]{1,64}$/)
719
+ .optional(),
720
+ }),
721
+ }),
722
+ toolUseKind: 'edit',
723
+ },
724
+ WaitForMcpServers: {
725
+ ...tool({
726
+ description: 'Wait for MCP servers that are still connecting',
727
+ inputSchema: z.object({
728
+ servers: z.array(z.string()).optional(),
729
+ }),
730
+ }),
731
+ toolUseKind: 'readonly',
732
+ },
733
+ Workflow: {
734
+ ...tool({
735
+ description: 'Run or resume a dynamic multi-agent workflow',
736
+ inputSchema: z.object({
737
+ script: z.string().optional(),
738
+ name: z.string().optional(),
739
+ description: z.string().optional(),
740
+ title: z.string().optional(),
741
+ args: z.record(z.string(), z.unknown()).optional(),
742
+ scriptPath: z.string().optional(),
743
+ resumeFromRunId: z.string().optional(),
744
+ }),
745
+ }),
746
+ toolUseKind: 'edit',
747
+ },
394
748
  } as const satisfies Record<string, HarnessV1BuiltinTool<any, any>>;
395
749
 
396
750
  /*
@@ -437,6 +791,14 @@ type ClaudeCodeBridgeCoords = z.infer<typeof claudeCodeBridgeCoordsSchema>;
437
791
  export function createClaudeCode(
438
792
  settings: ClaudeCodeHarnessSettings = {},
439
793
  ): HarnessV1<typeof CLAUDE_CODE_BUILTIN_TOOLS> {
794
+ if (
795
+ settings.mcpServers != null &&
796
+ Object.prototype.hasOwnProperty.call(settings.mcpServers, 'harness-tools')
797
+ ) {
798
+ throw new Error(
799
+ 'Claude Code MCP server name "harness-tools" is reserved for HarnessAgent tools.',
800
+ );
801
+ }
440
802
  let cachedBootstrap: HarnessV1Bootstrap | undefined;
441
803
  const thinking = settings.thinking ?? {
442
804
  type: 'adaptive',
@@ -566,6 +928,7 @@ export function createClaudeCode(
566
928
  permissionMode: startOpts.permissionMode,
567
929
  builtinToolFiltering: startOpts.builtinToolFiltering,
568
930
  skills: startOpts.skills ?? [],
931
+ mcpServers: settings.mcpServers,
569
932
  });
570
933
  } catch {
571
934
  // Bridge no longer reachable — recover by respawning below.
@@ -604,7 +967,10 @@ export function createClaudeCode(
604
967
  })
605
968
  : undefined;
606
969
  const port = resolveBridgePort(sandboxSession, settings.port);
607
- const token = randomBytes(32).toString('hex');
970
+ const token =
971
+ settings.mintBridgeToken == null
972
+ ? randomBytes(32).toString('hex')
973
+ : settings.mintBridgeToken(sandboxId);
608
974
  const authEnv = resolveClaudeCodeEnv(settings.auth);
609
975
  const env = {
610
976
  ...authEnv,
@@ -737,6 +1103,7 @@ export function createClaudeCode(
737
1103
  permissionMode: startOpts.permissionMode,
738
1104
  builtinToolFiltering: startOpts.builtinToolFiltering,
739
1105
  skills: startOpts.skills ?? [],
1106
+ mcpServers: settings.mcpServers,
740
1107
  });
741
1108
  },
742
1109
  };
@@ -980,6 +1347,7 @@ function createSession({
980
1347
  permissionMode,
981
1348
  builtinToolFiltering,
982
1349
  skills,
1350
+ mcpServers,
983
1351
  }: {
984
1352
  sessionId: string;
985
1353
  channel: ClaudeCodeChannel;
@@ -999,6 +1367,7 @@ function createSession({
999
1367
  permissionMode: HarnessV1PermissionMode | undefined;
1000
1368
  builtinToolFiltering: HarnessV1BuiltinToolFiltering | undefined;
1001
1369
  skills: ReadonlyArray<HarnessV1Skill>;
1370
+ mcpServers: Record<string, unknown> | undefined;
1002
1371
  }): HarnessV1Session {
1003
1372
  let stopped = false;
1004
1373
  let stopPromise: Promise<void> | undefined;
@@ -1009,13 +1378,6 @@ function createSession({
1009
1378
  * `attach`ed bridge is already past its first turn and continues on its own.
1010
1379
  */
1011
1380
  let pendingResumeFlag = continueOnFirstPrompt;
1012
- /*
1013
- * Instructions are prepended to the first user message of a fresh session
1014
- * only. A resumed session (attach/replay/rerun) already carried them in its
1015
- * original first message (preserved in the workdir snapshot), so it starts
1016
- * "applied".
1017
- */
1018
- let instructionsApplied = isResume;
1019
1381
 
1020
1382
  /*
1021
1383
  * Wire the channel into one turn's worth of events and return the control
@@ -1162,20 +1524,17 @@ function createSession({
1162
1524
  abortSignal: promptOpts.abortSignal,
1163
1525
  });
1164
1526
 
1165
- let promptText = extractUserText(promptOpts.prompt);
1166
- if (!instructionsApplied && promptOpts.instructions) {
1167
- promptText = frameInstructions(promptOpts.instructions, promptText);
1168
- }
1169
- instructionsApplied = true;
1170
-
1171
1527
  const startMessage = {
1172
1528
  type: 'start' as const,
1173
- prompt: promptText,
1529
+ prompt: extractUserText(promptOpts.prompt),
1174
1530
  tools: (promptOpts.tools ?? []).map(t => ({
1175
1531
  name: t.name,
1176
1532
  description: t.description,
1177
1533
  inputSchema: t.inputSchema,
1178
1534
  })),
1535
+ ...(promptOpts.instructions
1536
+ ? { instructions: promptOpts.instructions }
1537
+ : {}),
1179
1538
  model,
1180
1539
  maxTurns,
1181
1540
  ...(env !== undefined ? { env } : {}),
@@ -1183,6 +1542,7 @@ function createSession({
1183
1542
  ...(skills.length > 0
1184
1543
  ? { skills: skills.map(skill => skill.name) }
1185
1544
  : {}),
1545
+ ...(mcpServers == null ? {} : { mcpServers }),
1186
1546
  ...(permissionMode ? { permissionMode } : {}),
1187
1547
  ...(builtinToolFiltering ? { builtinToolFiltering } : {}),
1188
1548
  ...(debug ? { debug } : {}),
@@ -1229,6 +1589,9 @@ function createSession({
1229
1589
  description: t.description,
1230
1590
  inputSchema: t.inputSchema,
1231
1591
  })),
1592
+ ...(continueOpts.instructions
1593
+ ? { instructions: continueOpts.instructions }
1594
+ : {}),
1232
1595
  model,
1233
1596
  maxTurns,
1234
1597
  ...(env !== undefined ? { env } : {}),
@@ -1236,6 +1599,7 @@ function createSession({
1236
1599
  ...(skills.length > 0
1237
1600
  ? { skills: skills.map(skill => skill.name) }
1238
1601
  : {}),
1602
+ ...(mcpServers == null ? {} : { mcpServers }),
1239
1603
  ...(permissionMode ? { permissionMode } : {}),
1240
1604
  ...(builtinToolFiltering ? { builtinToolFiltering } : {}),
1241
1605
  ...(debug ? { debug } : {}),
@@ -1423,23 +1787,6 @@ function createSession({
1423
1787
  };
1424
1788
  }
1425
1789
 
1426
- /*
1427
- * Frame session instructions and the user's text so the runtime treats the
1428
- * instructions as system-provided operating guidance, not something the user
1429
- * wrote. Without the wrapper the agent can echo the prepended text back as if
1430
- * the user had asked for it, which is confusing since the user never typed it.
1431
- * Applied only to the first user message of a fresh session.
1432
- */
1433
- function frameInstructions(instructions: string, userText: string): string {
1434
- return (
1435
- '<session-instructions>\n' +
1436
- 'The block below is operating guidance from the system, not a message from the user — follow it, but do not mention it or attribute it to the user.\n\n' +
1437
- `${instructions}\n` +
1438
- '</session-instructions>\n\n' +
1439
- `<user-message>\n${userText}\n</user-message>`
1440
- );
1441
- }
1442
-
1443
1790
  /*
1444
1791
  * Reduce a `HarnessV1Prompt` to the plain user text the bridge forwards
1445
1792
  * to the Claude SDK. File and image parts on the message are not yet