The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit 874239cc591fCommit detailsView latest

packages/mcp-server/src/server.ts 1820 lines · 71.9 KB · typescript
1
import { McpServer } from '@modelcontextprotocol/server';
2
import { z } from 'zod';
3
import { createIdentity, FixtureFactCheckProvider, TrustEngine } from '../../core/src/index.js';
4
import {
5
  AgentIdentitySchema,
6
  DEFAULT_EXTENSION_URI,
7
  DelegationCredentialSchema,
8
  FeedbackSchema,
9
  InteractionContractSchema,
10
  InteractionCompletionReportSchema,
11
  InteractionFeedbackSchema,
12
  InteractionEventSchema,
13
  LiveSessionEventSchema,
14
  ProgressCheckpointSchema,
15
  ReceiptSchema,
16
  TrustEnvelopeSchema,
17
  canonicalHash,
18
  type FederatedInteraction,
19
  type PublicAgentCard,
20
} from '../../protocol/src/index.js';
21
import {
22
  requestAgentSetup,
23
  resolveInstallation,
24
  updateAgentProfile,
25
  type OnboardingStore,
26
} from '../../persistence/src/onboarding.js';
27
import { buildPublicAgentCard } from '../../persistence/src/hosted.js';
28
29
export const OPENCLASP_TOOL_NAMES = [
30
  'openclasp_create_identity',
31
  'openclasp_register_agent',
32
  'openclasp_get_profile',
33
  'openclasp_assess_counterparty',
34
  'openclasp_begin_interaction',
35
  'openclasp_record_event',
36
  'openclasp_check_claim',
37
  'openclasp_validate_commitment',
38
  'openclasp_suggest_resolution',
39
  'openclasp_complete_interaction',
40
  'openclasp_submit_feedback',
41
  'openclasp_raise_dispute',
42
  'openclasp_verify_receipt',
43
  'openclasp_setup',
44
  'openclasp_get_identity',
45
  'openclasp_switch_agent',
46
  'openclasp_update_profile',
47
  'openclasp_connection_status',
48
  'openclasp_register_delegation',
49
  'openclasp_save_contract',
50
  'openclasp_permit_mediation',
51
  'openclasp_resolve_dispute',
52
  'openclasp_find_agent',
53
  'openclasp_search_agents',
54
  'openclasp_connect_to_agent',
55
  'openclasp_list_invitations',
56
  'openclasp_respond_invitation',
57
  'openclasp_get_shared_interaction',
58
  'openclasp_get_live_session',
59
  'openclasp_record_session_event',
60
  'openclasp_complete_live_session',
61
  'openclasp_heartbeat',
62
  'openclasp_list_threads',
63
  'openclasp_get_thread',
64
  'openclasp_send_message',
65
  'openclasp_reply',
66
  'openclasp_mark_read',
67
  'openclasp_close_thread',
68
  'openclasp_submit_completion_report',
69
  'openclasp_list_feedback_requests',
70
  'openclasp_submit_interaction_feedback',
71
  'openclasp_checkpoint',
72
  'openclasp_resolve_agent',
73
  'openclasp_propose_contract_revision',
74
  'openclasp_respond_contract_revision',
75
] as const;
76
77
export const HOSTED_OPENCLASP_TOOL_NAMES = OPENCLASP_TOOL_NAMES.filter(
78
  (name) => name !== 'openclasp_create_identity',
79
);
80
81
const READ_ONLY_TOOL = {
82
  readOnlyHint: true,
83
  destructiveHint: false,
84
  idempotentHint: true,
85
} as const;
86
const WRITE_TOOL = {
87
  readOnlyHint: false,
88
  destructiveHint: false,
89
  idempotentHint: false,
90
} as const;
91
92
const text = (value: unknown) => ({
93
  content: [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }],
94
});
95
96
const LiveSessionEventInputSchema = z
97
  .object({
98
    interactionId: LiveSessionEventSchema.shape.interactionId,
99
    sequence: LiveSessionEventSchema.shape.sequence,
100
    type: LiveSessionEventSchema.shape.type,
101
    occurredAt: LiveSessionEventSchema.shape.occurredAt,
102
    messageHash: LiveSessionEventSchema.shape.messageHash,
103
    evidenceReferences: LiveSessionEventSchema.shape.evidenceReferences,
104
    outcome: LiveSessionEventSchema.shape.outcome,
105
    checkpoint: LiveSessionEventSchema.shape.checkpoint,
106
    details: LiveSessionEventSchema.shape.details,
107
  })
108
  .strict()
109
  .superRefine((event, context) => {
110
    if (event.type === 'progress_checkpoint' && !event.checkpoint)
111
      context.addIssue({
112
        code: 'custom',
113
        path: ['checkpoint'],
114
        message: 'Progress checkpoint events require structured checkpoint data',
115
      });
116
    if (event.type !== 'progress_checkpoint' && event.checkpoint)
117
      context.addIssue({
118
        code: 'custom',
119
        path: ['checkpoint'],
120
        message: 'Checkpoint data is only valid on progress checkpoint events',
121
      });
122
  });
123
124
export const OPENCLASP_MCP_INSTRUCTIONS =
125
  'Start with openclasp_connection_status; heartbeat while active. Resolve agent references with openclasp_resolve_agent. Persistent runtimes use direct A2A; temporary agents use hosted threads. Use contract revision tools for high-stakes terms or amendments. For long tasks call openclasp_checkpoint every five exchanges or when blocked, drifting, or nearly done. At a terminal outcome call openclasp_complete_live_session with honest structured feedback. Never upload transcripts or invent evidence or feedback.';
126
127
export function buildMcpServer(engine = new TrustEngine()) {
128
  const server = new McpServer(
129
    { name: 'openclasp', version: '0.1.0' },
130
    {
131
      instructions: OPENCLASP_MCP_INSTRUCTIONS,
132
    },
133
  );
134
  return registerOpenClaspTools(server, engine);
135
}
136
137
type HostedRecord = (
138
  operatorId: string,
139
  kind:
140
    | 'agent'
141
    | 'delegation'
142
    | 'contract'
143
    | 'interaction'
144
    | 'event'
145
    | 'receipt'
146
    | 'feedback'
147
    | 'conflict'
148
    | 'profile',
149
  recordId: string,
150
  value: unknown,
151
) => Promise<void>;
152
153
type ToolContext = {
154
  http?: {
155
    authInfo?: { clientId?: string; extra?: Record<string, unknown> };
156
  };
157
};
158
type EngineSource = TrustEngine | ((context: ToolContext) => Promise<TrustEngine>);
159
type AgentDirectory = {
160
  publishAgent?(operatorId: string, card: PublicAgentCard): Promise<PublicAgentCard>;
161
  getPublishedAgent(agentId: string): Promise<PublicAgentCard | undefined>;
162
  resolveAgentReference?(reference: string): Promise<any | undefined>;
163
  searchPublishedAgents(input: {
164
    query?: string | undefined;
165
    capability?: string | undefined;
166
    limit?: number | undefined;
167
  }): Promise<PublicAgentCard[]>;
168
  createFederatedInteraction(
169
    operatorId: string,
170
    value: FederatedInteraction,
171
  ): Promise<FederatedInteraction>;
172
  listFederatedInteractions(operatorId: string): Promise<FederatedInteraction[]>;
173
  getFederatedInteraction(
174
    operatorId: string,
175
    interactionId: string,
176
  ): Promise<FederatedInteraction | undefined>;
177
  respondToFederatedInteraction(
178
    operatorId: string,
179
    interactionId: string,
180
    agentId: string,
181
    decision: 'accept' | 'reject',
182
    method?: 'oauth_installation' | 'oauth_account' | 'policy_auto_accept',
183
  ): Promise<FederatedInteraction>;
184
  proposeContractRevision?(
185
    operatorId: string,
186
    interactionId: string,
187
    agentId: string,
188
    contract: z.infer<typeof InteractionContractSchema>,
189
    expectedTermsHash?: string,
190
    method?: 'oauth_installation' | 'oauth_account',
191
  ): Promise<FederatedInteraction>;
192
  respondToContractRevision?(
193
    operatorId: string,
194
    interactionId: string,
195
    agentId: string,
196
    revisionId: string,
197
    decision: 'accept' | 'reject',
198
    method?: 'oauth_installation' | 'oauth_account',
199
  ): Promise<FederatedInteraction>;
200
  getLiveSession(operatorId: string, interactionId: string, agentId: string): Promise<any>;
201
  recordLiveSessionEvent(
202
    token: string,
203
    event: z.infer<typeof LiveSessionEventSchema>,
204
  ): Promise<unknown>;
205
  submitCompletionReport?(
206
    operatorId: string,
207
    agentId: string,
208
    report: z.infer<typeof InteractionCompletionReportSchema>,
209
    submissionMethod: 'oauth_installation' | 'agent_access_token' | 'runtime_session',
210
  ): Promise<unknown>;
211
  listFeedbackRequests?(operatorId: string, agentId: string): Promise<unknown[]>;
212
  submitInteractionFeedback?(
213
    operatorId: string,
214
    agentId: string,
215
    feedback: z.infer<typeof InteractionFeedbackSchema>,
216
    submissionMethod: 'oauth_installation' | 'agent_access_token' | 'runtime_session',
217
  ): Promise<unknown>;
218
  touchAgentPresence(operatorId: string, agentId: string): Promise<unknown>;
219
  listHostedThreads(operatorId: string, agentId: string): Promise<any[]>;
220
  getHostedThread(operatorId: string, agentId: string, threadId: string): Promise<any>;
221
  sendTemporaryMessage(
222
    operatorId: string,
223
    agentId: string,
224
    interactionId: string,
225
    content: string,
226
  ): Promise<any>;
227
  markHostedThreadRead(operatorId: string, agentId: string, threadId: string): Promise<any>;
228
  closeHostedThread(operatorId: string, agentId: string, threadId: string): Promise<any>;
229
};
230
231
function installationContext(context: ToolContext) {
232
  const operatorId = context.http?.authInfo?.extra?.operatorId;
233
  const clientId = context.http?.authInfo?.clientId;
234
  if (typeof operatorId !== 'string' || typeof clientId !== 'string')
235
    throw new Error('Authenticated MCP installation context is required');
236
  const boundAgentId = context.http?.authInfo?.extra?.boundAgentId;
237
  const credentialType = context.http?.authInfo?.extra?.credentialType;
238
  return {
239
    operatorId,
240
    clientId,
241
    ...(typeof boundAgentId === 'string' ? { boundAgentId } : {}),
242
    ...(typeof credentialType === 'string' ? { credentialType } : {}),
243
  };
244
}
245
246
export function registerOpenClaspTools(
247
  server: McpServer,
248
  engineSource: EngineSource = new TrustEngine(),
249
  recordHosted?: HostedRecord,
250
  onboardingStore?: OnboardingStore,
251
  agentDirectory?: AgentDirectory,
252
) {
253
  const factChecker = new FixtureFactCheckProvider();
254
  const resolveEngine = (context: ToolContext) =>
255
    typeof engineSource === 'function' ? engineSource(context) : Promise.resolve(engineSource);
256
  const persist = async (
257
    context: ToolContext,
258
    kind: Parameters<HostedRecord>[1],
259
    id: string,
260
    value: unknown,
261
  ) => {
262
    const operatorId = context.http?.authInfo?.extra?.operatorId;
263
    if (recordHosted && typeof operatorId === 'string')
264
      await recordHosted(operatorId, kind, id, value);
265
  };
266
267
  const resolveAuthenticatedInstallation = async (context: ToolContext) => {
268
    if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
269
    const connection = installationContext(context);
270
    const binding = await resolveInstallation(
271
      onboardingStore,
272
      connection.operatorId,
273
      connection.clientId,
274
    );
275
    if (
276
      connection.boundAgentId &&
277
      (binding.status !== 'connected' || binding.agent.agentId !== connection.boundAgentId)
278
    )
279
      throw new Error('Agent access token binding is invalid');
280
    return { connection, binding };
281
  };
282
283
  const requireBoundAgent = async (context: ToolContext, claimedAgentId?: string) => {
284
    if (!onboardingStore) return undefined;
285
    const { connection, binding } = await resolveAuthenticatedInstallation(context);
286
    if (binding.status !== 'connected')
287
      throw new Error('Call openclasp_setup and obtain owner confirmation before using this tool');
288
    if (claimedAgentId && binding.agent.agentId !== claimedAgentId)
289
      throw new Error('The claimed agent does not match this MCP installation');
290
    if (agentDirectory)
291
      await agentDirectory.touchAgentPresence(connection.operatorId, binding.agent.agentId);
292
    return binding;
293
  };
294
295
  if (!onboardingStore)
296
    server.registerTool(
297
      OPENCLASP_TOOL_NAMES[0],
298
      {
299
        title: 'Create local cryptographic identity',
300
        description:
301
          'Create an Ed25519 identity for local development. This returns private key material and is intentionally unavailable on hosted MCP.',
302
        inputSchema: z.object({
303
          agentId: z.string(),
304
          operatorRef: z.string(),
305
          capabilities: z.array(z.string()),
306
        }),
307
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
308
      },
309
      async (input, context) => {
310
        const authenticatedOperator = context.http?.authInfo?.extra?.operatorId;
311
        const created = createIdentity({
312
          ...input,
313
          operatorRef:
314
            typeof authenticatedOperator === 'string'
315
              ? `auth0:${authenticatedOperator}`
316
              : input.operatorRef,
317
        });
318
        return text(created);
319
      },
320
    );
321
  server.registerTool(
322
    OPENCLASP_TOOL_NAMES[1],
323
    {
324
      title: 'Register cryptographic identity',
325
      description: 'Register and verify a signed agent identity',
326
      inputSchema: AgentIdentitySchema,
327
      annotations: { ...WRITE_TOOL, idempotentHint: true },
328
    },
329
    async (input, context) => {
330
      const authenticatedOperator = context.http?.authInfo?.extra?.operatorId;
331
      if (
332
        typeof authenticatedOperator === 'string' &&
333
        input.operatorRef !== `auth0:${authenticatedOperator}`
334
      ) {
335
        throw new Error('Agent identity is not owned by the authenticated operator');
336
      }
337
      await requireBoundAgent(context, input.agentId);
338
      const engine = await resolveEngine(context);
339
      const registered = engine.registerAgent(input);
340
      await persist(context, 'agent', registered.agentId, registered);
341
      return text(registered);
342
    },
343
  );
344
  server.registerTool(
345
    OPENCLASP_TOOL_NAMES[2],
346
    {
347
      title: 'Get contextual reliability',
348
      description: 'Get a task-specific behavioural profile',
349
      inputSchema: z.object({ agentId: z.string(), version: z.string(), taskCategory: z.string() }),
350
      annotations: READ_ONLY_TOOL,
351
    },
352
    async (input, context) => {
353
      await requireBoundAgent(context);
354
      const engine = await resolveEngine(context);
355
      return text(engine.getRisk(input.agentId, input.version, input.taskCategory));
356
    },
357
  );
358
  server.registerTool(
359
    OPENCLASP_TOOL_NAMES[3],
360
    {
361
      title: 'Assess counterparty',
362
      description: 'Assess a counterparty in context',
363
      inputSchema: z.object({
364
        envelope: TrustEnvelopeSchema,
365
        action: z.string(),
366
        dataClasses: z.array(z.string()).optional(),
367
        humanApproved: z.boolean().optional(),
368
      }),
369
      annotations: READ_ONLY_TOOL,
370
    },
371
    async (input, context) => {
372
      await requireBoundAgent(context);
373
      const engine = await resolveEngine(context);
374
      return text(engine.assess(input as any));
375
    },
376
  );
377
  server.registerTool(
378
    OPENCLASP_TOOL_NAMES[4],
379
    {
380
      title: 'Begin assured interaction',
381
      description: 'Begin an interaction and return its identifier',
382
      inputSchema: z.object({
383
        purpose: z.string(),
384
        parties: z.array(z.string()),
385
        taskCategory: z.string(),
386
      }),
387
      annotations: WRITE_TOOL,
388
    },
389
    async (input, context) => {
390
      const binding = await requireBoundAgent(context);
391
      if (binding && !input.parties.includes(binding.agent.agentId))
392
        throw new Error('Interaction parties must include the agent bound to this installation');
393
      const interaction = {
394
        interactionId: crypto.randomUUID(),
395
        status: 'contract_required',
396
        createdAt: new Date().toISOString(),
397
        ...input,
398
      };
399
      await persist(context, 'interaction', interaction.interactionId, interaction);
400
      return text(interaction);
401
    },
402
  );
403
  server.registerTool(
404
    OPENCLASP_TOOL_NAMES[5],
405
    {
406
      title: 'Record signed event',
407
      description: 'Validate and record a signed structured event',
408
      inputSchema: InteractionEventSchema,
409
      annotations: { ...WRITE_TOOL, idempotentHint: true },
410
    },
411
    async (input, context) => {
412
      await requireBoundAgent(context, input.agentId);
413
      const engine = await resolveEngine(context);
414
      const event = engine.recordEvent(input);
415
      await persist(context, 'event', event.eventId, event);
416
      return text(event);
417
    },
418
  );
419
  server.registerTool(
420
    OPENCLASP_TOOL_NAMES[6],
421
    {
422
      title: 'Check factual claim',
423
      description: 'Check an objective factual claim',
424
      inputSchema: z.object({ claim: z.string(), permission: z.boolean().optional() }),
425
      annotations: { ...READ_ONLY_TOOL, openWorldHint: true },
426
    },
427
    async (input) => text(await factChecker.check(input.claim, input.permission)),
428
  );
429
  server.registerTool(
430
    OPENCLASP_TOOL_NAMES[7],
431
    {
432
      title: 'Validate commitment',
433
      description: 'Check whether a commitment is represented by a signed event',
434
      inputSchema: z.object({ interactionId: z.string().uuid(), commitment: z.string() }),
435
      annotations: READ_ONLY_TOOL,
436
    },
437
    async (input, context) => {
438
      await requireBoundAgent(context);
439
      const engine = await resolveEngine(context);
440
      return text({
441
        valid: [...engine.events.values()].some(
442
          (event) =>
443
            event.interactionId === input.interactionId &&
444
            event.eventType === 'commitment' &&
445
            JSON.stringify(event.payload).includes(input.commitment),
446
        ),
447
      });
448
    },
449
  );
450
  server.registerTool(
451
    OPENCLASP_TOOL_NAMES[8],
452
    {
453
      title: 'Suggest dispute resolution',
454
      description: 'Suggest attributable resolutions for a consented conflict',
455
      inputSchema: z.object({ conflictId: z.string() }),
456
      annotations: READ_ONLY_TOOL,
457
    },
458
    async (input, context) => {
459
      const binding = await requireBoundAgent(context);
460
      const engine = await resolveEngine(context);
461
      const conflict = engine.conflicts.get(input.conflictId);
462
      if (binding && (!conflict || !(binding.agent.agentId in conflict.permissions)))
463
        throw new Error('The bound agent is not a participant in this dispute');
464
      return text(engine.conflicts.get(input.conflictId)?.possibleResolutions ?? []);
465
    },
466
  );
467
  server.registerTool(
468
    OPENCLASP_TOOL_NAMES[9],
469
    {
470
      title: 'Complete interaction',
471
      description: 'Submit a signed completion receipt',
472
      inputSchema: ReceiptSchema,
473
      annotations: { ...WRITE_TOOL, idempotentHint: true },
474
    },
475
    async (input, context) => {
476
      const binding = await requireBoundAgent(context);
477
      if (binding && !input.participants.includes(binding.agent.agentId))
478
        throw new Error('Receipt participants must include the bound agent');
479
      const engine = await resolveEngine(context);
480
      const receipt = engine.submitReceipt(input);
481
      await persist(context, 'receipt', receipt.receiptId, receipt);
482
      return text(receipt);
483
    },
484
  );
485
  server.registerTool(
486
    OPENCLASP_TOOL_NAMES[10],
487
    {
488
      title: 'Submit bilateral feedback',
489
      description: 'Submit receipt-backed structured feedback',
490
      inputSchema: FeedbackSchema,
491
      annotations: { ...WRITE_TOOL, idempotentHint: true },
492
    },
493
    async (input, context) => {
494
      await requireBoundAgent(context, input.reviewerAgentId);
495
      const engine = await resolveEngine(context);
496
      const result = engine.submitFeedback(input);
497
      await persist(context, 'feedback', input.feedbackId, input);
498
      for (const profile of engine.profiles.values())
499
        await persist(
500
          context,
501
          'profile',
502
          `${profile.agentId}|${profile.agentVersion}|${profile.taskCategory}`,
503
          profile,
504
        );
505
      return text(result);
506
    },
507
  );
508
  server.registerTool(
509
    OPENCLASP_TOOL_NAMES[11],
510
    {
511
      title: 'Raise dispute',
512
      description: 'Create a mutually consented dispute',
513
      inputSchema: z.object({
514
        interactionId: z.string().uuid(),
515
        issue: z.string(),
516
        participants: z.array(z.string()).min(2),
517
      }),
518
      annotations: WRITE_TOOL,
519
    },
520
    async (input, context) => {
521
      const binding = await requireBoundAgent(context);
522
      if (binding && !input.participants.includes(binding.agent.agentId))
523
        throw new Error('Dispute participants must include the bound agent');
524
      const engine = await resolveEngine(context);
525
      const conflict = engine.createConflict({
526
        interactionId: input.interactionId,
527
        issue: input.issue,
528
        participants: input.participants,
529
        positions: {},
530
        evidence: [],
531
        contractClauses: [],
532
        missingInformation: [],
533
        possibleResolutions: [],
534
      });
535
      await persist(context, 'conflict', conflict.conflictId, conflict);
536
      return text(conflict);
537
    },
538
  );
539
  server.registerTool(
540
    OPENCLASP_TOOL_NAMES[12],
541
    {
542
      title: 'Verify receipt',
543
      description: 'Verify a signed interaction receipt',
544
      inputSchema: ReceiptSchema,
545
      annotations: READ_ONLY_TOOL,
546
    },
547
    async (input, context) => {
548
      await requireBoundAgent(context);
549
      const engine = await resolveEngine(context);
550
      try {
551
        engine.verifyReceipt(input);
552
        return text({ valid: true });
553
      } catch {
554
        return text({ valid: false });
555
      }
556
    },
557
  );
558
  server.registerTool(
559
    OPENCLASP_TOOL_NAMES[13],
560
    {
561
      title: 'Set up this agent',
562
      description:
563
        'Propose this installation and safe automation policy. Temporary chats receive an OpenClasp-hosted A2A endpoint; persistent agents connect their own runtime.',
564
      inputSchema: z
565
        .object({
566
          agentName: z.string().trim().min(1).max(100),
567
          projectName: z.string().trim().min(1).max(100).optional(),
568
          projectId: z.string().optional(),
569
          framework: z.string().trim().max(100).optional(),
570
          description: z.string().trim().max(500).optional(),
571
          agentVersion: z.string().trim().min(1).max(100).optional(),
572
          agentMode: z.enum(['persistent_runtime', 'temporary_chat']).optional(),
573
          autoPublish: z.boolean().optional(),
574
          autoAcceptPolicy: z.enum(['off', 'safe_matching']).optional(),
575
          autoAcceptTaskCategories: z.array(z.string().trim().min(1).max(100)).max(100).optional(),
576
          capabilities: z.array(z.string().trim().min(1).max(100)).max(100).optional(),
577
          limitations: z.array(z.string().trim().min(1).max(300)).max(100).optional(),
578
        })
579
        .refine((input) => input.projectName || input.projectId, {
580
          message: 'projectName or projectId is required',
581
        }),
582
      annotations: WRITE_TOOL,
583
    },
584
    async (input, context) => {
585
      if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
586
      const { connection, binding: current } = await resolveAuthenticatedInstallation(context);
587
      if (current.status === 'connected') {
588
        await agentDirectory?.touchAgentPresence(connection.operatorId, current.agent.agentId);
589
        return text(current);
590
      }
591
      const request = await requestAgentSetup(onboardingStore, connection.operatorId, {
592
        clientId: connection.clientId,
593
        ...input,
594
      });
595
      return text({
596
        status: 'pending_confirmation',
597
        confirmationRequired: true,
598
        request,
599
        confirmationUrl: `${process.env.OPENCLASP_DASHBOARD_URL ?? 'https://openclasp.vercel.app'}/connect`,
600
        next: 'Ask the owner to confirm this setup request in the OpenClasp dashboard, then call openclasp_get_identity.',
601
      });
602
    },
603
  );
604
  server.registerTool(
605
    OPENCLASP_TOOL_NAMES[14],
606
    {
607
      title: 'Get my agent identity',
608
      description:
609
        'Return the agent identity automatically bound to this authenticated MCP installation.',
610
      inputSchema: z.object({}),
611
      annotations: READ_ONLY_TOOL,
612
    },
613
    async (_input, context) => {
614
      if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
615
      const { connection, binding } = await resolveAuthenticatedInstallation(context);
616
      if (binding.status === 'connected')
617
        await agentDirectory?.touchAgentPresence(connection.operatorId, binding.agent.agentId);
618
      return text(binding);
619
    },
620
  );
621
  server.registerTool(
622
    OPENCLASP_TOOL_NAMES[15],
623
    {
624
      title: 'Switch agent identity',
625
      description:
626
        'Request that this MCP installation switch to another existing agent. Dashboard confirmation is required.',
627
      inputSchema: z.object({ existingAgentId: z.string().min(1) }),
628
      annotations: WRITE_TOOL,
629
    },
630
    async (input, context) => {
631
      if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
632
      const connection = installationContext(context);
633
      if (connection.credentialType === 'agent_access_token')
634
        throw new Error('Agent access tokens cannot switch agent identity');
635
      const request = await requestAgentSetup(onboardingStore, connection.operatorId, {
636
        clientId: connection.clientId,
637
        action: 'switch',
638
        existingAgentId: input.existingAgentId,
639
      });
640
      return text({
641
        status: 'pending_confirmation',
642
        confirmationRequired: true,
643
        request,
644
        confirmationUrl: `${process.env.OPENCLASP_DASHBOARD_URL ?? 'https://openclasp.vercel.app'}/connect`,
645
      });
646
    },
647
  );
648
  server.registerTool(
649
    OPENCLASP_TOOL_NAMES[16],
650
    {
651
      title: 'Update my agent profile',
652
      description: 'Update the profile of the agent bound to this MCP installation.',
653
      inputSchema: z.object({
654
        name: z.string().trim().min(1).max(100).optional(),
655
        framework: z.string().trim().min(1).max(100).optional(),
656
        description: z.string().trim().max(500).optional(),
657
        agentVersion: z.string().trim().min(1).max(100).optional(),
658
        agentMode: z.enum(['persistent_runtime', 'temporary_chat']).optional(),
659
        autoPublish: z.boolean().optional(),
660
        autoAcceptPolicy: z.enum(['off', 'safe_matching']).optional(),
661
        autoAcceptTaskCategories: z.array(z.string().trim().min(1).max(100)).max(100).optional(),
662
        capabilities: z.array(z.string().trim().min(1).max(100)).max(100).optional(),
663
        limitations: z.array(z.string().trim().min(1).max(300)).max(100).optional(),
664
      }),
665
      annotations: WRITE_TOOL,
666
    },
667
    async (input, context) => {
668
      if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
669
      const { connection } = await resolveAuthenticatedInstallation(context);
670
      const agent = await updateAgentProfile(
671
        onboardingStore,
672
        connection.operatorId,
673
        connection.clientId,
674
        input,
675
      );
676
      const published = await agentDirectory?.getPublishedAgent(agent.agentId);
677
      if (published && agentDirectory?.publishAgent)
678
        await agentDirectory.publishAgent(
679
          connection.operatorId,
680
          buildPublicAgentCard(
681
            agent,
682
            process.env.OPENCLASP_PUBLIC_URL ??
683
              process.env.OPENCLASP_DASHBOARD_URL ??
684
              'https://openclasp.vercel.app',
685
            published,
686
          ),
687
        );
688
      return text(agent);
689
    },
690
  );
691
  server.registerTool(
692
    OPENCLASP_TOOL_NAMES[17],
693
    {
694
      title: 'Check OpenClasp connection',
695
      description:
696
        'Check whether this MCP installation is unbound, awaiting confirmation, or connected.',
697
      inputSchema: z.object({}),
698
      annotations: READ_ONLY_TOOL,
699
    },
700
    async (_input, context) => {
701
      if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
702
      const { connection, binding } = await resolveAuthenticatedInstallation(context);
703
      if (binding.status === 'connected') {
704
        await agentDirectory?.touchAgentPresence(connection.operatorId, binding.agent.agentId);
705
        return text(binding);
706
      }
707
      const state = await onboardingStore.list(connection.operatorId);
708
      const pending = state
709
        .filter((row) => row.kind === 'setup_request')
710
        .map((row) => row.payload as { clientId?: string; status?: string })
711
        .find(
712
          (request) => request.clientId === connection.clientId && request.status === 'pending',
713
        );
714
      return text(
715
        pending
716
          ? {
717
              status: 'pending_confirmation',
718
              confirmationUrl: `${process.env.OPENCLASP_DASHBOARD_URL ?? 'https://openclasp.vercel.app'}/connect`,
719
            }
720
          : binding,
721
      );
722
    },
723
  );
724
  server.registerTool(
725
    OPENCLASP_TOOL_NAMES[18],
726
    {
727
      title: 'Register delegation',
728
      description: 'Register and verify a signed, scoped delegation between two known agents.',
729
      inputSchema: DelegationCredentialSchema,
730
      annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
731
    },
732
    async (input, context) => {
733
      const binding = await requireBoundAgent(context);
734
      if (
735
        binding &&
736
        input.parentAgentId !== binding.agent.agentId &&
737
        input.childAgentId !== binding.agent.agentId
738
      )
739
        throw new Error('The bound agent is not a party to this delegation');
740
      const engine = await resolveEngine(context);
741
      engine.delegations.set(input.delegationId, input);
742
      if (!engine.verifyDelegation(input.delegationId)) {
743
        engine.delegations.delete(input.delegationId);
744
        throw new Error('Delegation verification failed');
745
      }
746
      await persist(context, 'delegation', input.delegationId, input);
747
      return text({ valid: true, delegation: input });
748
    },
749
  );
750
  server.registerTool(
751
    OPENCLASP_TOOL_NAMES[19],
752
    {
753
      title: 'Save signed agreement',
754
      description:
755
        'Verify and save a complete interaction contract signed by every participating agent.',
756
      inputSchema: InteractionContractSchema,
757
      annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
758
    },
759
    async (input, context) => {
760
      const binding = await requireBoundAgent(context);
761
      if (binding && !input.parties.includes(binding.agent.agentId))
762
        throw new Error('Contract parties must include the bound agent');
763
      const contract = (await resolveEngine(context)).saveContract(input);
764
      await persist(context, 'contract', contract.interactionId, contract);
765
      return text(contract);
766
    },
767
  );
768
  server.registerTool(
769
    OPENCLASP_TOOL_NAMES[20],
770
    {
771
      title: 'Consent to mediation',
772
      description:
773
        "Record the bound agent's explicit consent to mediation for a dispute it participates in.",
774
      inputSchema: z.object({ conflictId: z.string(), agentId: z.string().optional() }),
775
      annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
776
    },
777
    async (input, context) => {
778
      const binding = await requireBoundAgent(context);
779
      const agentId = binding?.agent.agentId ?? input.agentId;
780
      if (!agentId) throw new Error('agentId is required for local MCP');
781
      const engine = await resolveEngine(context);
782
      const conflict = engine.permitMediation(input.conflictId, agentId);
783
      await persist(context, 'conflict', conflict.conflictId, conflict);
784
      return text(conflict);
785
    },
786
  );
787
  server.registerTool(
788
    OPENCLASP_TOOL_NAMES[21],
789
    {
790
      title: 'Resolve mediated dispute',
791
      description:
792
        'Save an agreed resolution only after every dispute participant has consented to mediation.',
793
      inputSchema: z.object({ conflictId: z.string(), resolution: z.string().trim().min(1) }),
794
      annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
795
    },
796
    async (input, context) => {
797
      const binding = await requireBoundAgent(context);
798
      const engine = await resolveEngine(context);
799
      const existing = engine.conflicts.get(input.conflictId);
800
      if (binding && (!existing || !(binding.agent.agentId in existing.permissions)))
801
        throw new Error('The bound agent is not a participant in this dispute');
802
      const conflict = engine.resolveConflict(input.conflictId, input.resolution);
803
      await persist(context, 'conflict', conflict.conflictId, conflict);
804
      return text(conflict);
805
    },
806
  );
807
  server.registerTool(
808
    OPENCLASP_TOOL_NAMES[22],
809
    {
810
      title: 'Find published agent',
811
      description:
812
        'Find an exact owner-published agent card. Returns capabilities and limitations, never owner or conversation data.',
813
      inputSchema: z.object({ agentId: z.string().min(1) }),
814
      annotations: READ_ONLY_TOOL,
815
    },
816
    async (input, context) => {
817
      await requireBoundAgent(context);
818
      if (!agentDirectory) throw new Error('The shared agent directory is not configured');
819
      return text((await agentDirectory.getPublishedAgent(input.agentId)) ?? { found: false });
820
    },
821
  );
822
  server.registerTool(
823
    OPENCLASP_TOOL_NAMES[23],
824
    {
825
      title: 'Search published agents',
826
      description:
827
        'Search owner-published agent cards by name, framework, or capability without exposing owner identities or projects.',
828
      inputSchema: z.object({
829
        query: z.string().trim().max(100).optional(),
830
        capability: z.string().trim().max(100).optional(),
831
        limit: z.number().int().min(1).max(50).optional(),
832
      }),
833
      annotations: READ_ONLY_TOOL,
834
    },
835
    async (input, context) => {
836
      await requireBoundAgent(context);
837
      if (!agentDirectory) throw new Error('The shared agent directory is not configured');
838
      return text(await agentDirectory.searchPublishedAgents(input));
839
    },
840
  );
841
  server.registerTool(
842
    OPENCLASP_TOOL_NAMES[24],
843
    {
844
      title: 'Automatically connect to another agent',
845
      description:
846
        'Infer safe contract defaults, auto-activate when the responder policy permits, and return a ready-to-send A2A request.',
847
      inputSchema: z
848
        .object({
849
          targetAgentId: z.string().min(1).optional(),
850
          targetAgentReference: z.string().trim().min(1).max(2048).optional(),
851
          targetAgentCardUrl: z.string().url().optional(),
852
          task: z.string().trim().min(1).max(2000).optional(),
853
          purpose: z.string().trim().min(1).max(500).optional(),
854
          taskCategory: z.string().trim().min(1).max(100).optional(),
855
          requestedOutcome: z.string().trim().min(1).max(1000).optional(),
856
          successCriteria: z.array(z.string().trim().min(1)).min(1).max(50).optional(),
857
          allowedActions: z.array(z.string().trim().min(1)).max(100).default([]),
858
          prohibitedActions: z.array(z.string().trim().min(1)).max(100).default([]),
859
          allowedData: z.array(z.string().trim().min(1)).max(100).default([]),
860
          prohibitedData: z.array(z.string().trim().min(1)).max(100).default([]),
861
          evidenceRequirements: z.array(z.string().trim().min(1)).max(100).default([]),
862
          deadline: z.string().datetime().optional(),
863
          expiresInMinutes: z.number().int().min(5).max(10080).default(60),
864
        })
865
        .superRefine((value, context) => {
866
          if (!value.targetAgentId && !value.targetAgentReference && !value.targetAgentCardUrl)
867
            context.addIssue({
868
              code: 'custom',
869
              message: 'targetAgentId, targetAgentReference, or targetAgentCardUrl is required',
870
            });
871
          if (!value.task && !value.purpose)
872
            context.addIssue({ code: 'custom', message: 'task or purpose is required' });
873
        }),
874
      annotations: { ...WRITE_TOOL, openWorldHint: true },
875
    },
876
    async (input, context) => {
877
      if (!agentDirectory) throw new Error('Federated agent connections are not configured');
878
      const binding = await requireBoundAgent(context);
879
      if (!binding) throw new Error('A hosted, bound MCP installation is required');
880
      const connection = installationContext(context);
881
      let initiatorCard = await agentDirectory.getPublishedAgent(binding.agent.agentId);
882
      if (!initiatorCard && binding.agent.autoPublish && agentDirectory.publishAgent)
883
        initiatorCard = await agentDirectory.publishAgent(
884
          connection.operatorId,
885
          buildPublicAgentCard(
886
            binding.agent,
887
            process.env.OPENCLASP_PUBLIC_URL ??
888
              process.env.OPENCLASP_DASHBOARD_URL ??
889
              'https://openclasp.vercel.app',
890
          ),
891
        );
892
      if (!initiatorCard)
893
        throw new Error(
894
          'This agent is private. Enable automatic publishing once in its dashboard settings.',
895
        );
896
      let targetAgentId = input.targetAgentId;
897
      let resolvedResponderCard: PublicAgentCard | undefined;
898
      if (!targetAgentId && input.targetAgentReference) {
899
        if (!agentDirectory.resolveAgentReference)
900
          throw new Error('Agent reference resolution is not configured');
901
        const resolution = await agentDirectory.resolveAgentReference(input.targetAgentReference);
902
        resolvedResponderCard = resolution?.card as PublicAgentCard | undefined;
903
        targetAgentId = resolvedResponderCard?.agentId;
904
      }
905
      if (!targetAgentId && input.targetAgentCardUrl) {
906
        const cardUrl = new URL(input.targetAgentCardUrl);
907
        const trustedOrigin = new URL(
908
          process.env.OPENCLASP_DASHBOARD_URL ?? 'https://openclasp.vercel.app',
909
        ).origin;
910
        const match = cardUrl.pathname.match(/^\/agents\/([^/]+)\/card\.json$/);
911
        if (cardUrl.origin !== trustedOrigin || !match?.[1])
912
          throw new Error('Only OpenClasp-hosted Agent Card URLs are accepted');
913
        targetAgentId = decodeURIComponent(match[1]);
914
      }
915
      if (!targetAgentId) throw new Error('Target agent is required');
916
      const responderCard =
917
        resolvedResponderCard ?? (await agentDirectory.getPublishedAgent(targetAgentId));
918
      if (!responderCard) throw new Error('Target agent is not published on OpenClasp');
919
      if (
920
        initiatorCard.agentMode === 'temporary_chat' &&
921
        responderCard.agentMode === 'temporary_chat'
922
      )
923
        throw new Error('Temporary-to-temporary conversations are not supported in this MVP');
924
      const responderTransport = responderCard.transports[0];
925
      if (!responderTransport) throw new Error('Target agent has not published an A2A endpoint');
926
      const task = input.task ?? input.purpose!;
927
      const purpose = input.purpose ?? task.slice(0, 500);
928
      const taskCategory = input.taskCategory ?? responderCard.capabilities[0] ?? 'general';
929
      const requestedOutcome = input.requestedOutcome ?? task.slice(0, 1000);
930
      const successCriteria = input.successCriteria ?? [
931
        'Return a clear result that directly addresses the requested task',
932
      ];
933
      const existing = (await agentDirectory.listFederatedInteractions(connection.operatorId)).find(
934
        (candidate) =>
935
          candidate.initiatorAgentId === binding.agent.agentId &&
936
          candidate.responderAgentId === responderCard.agentId &&
937
          ['pending', 'active'].includes(candidate.status) &&
938
          candidate.contract.purpose === purpose &&
939
          candidate.contract.taskCategory === taskCategory &&
940
          candidate.contract.requestedOutcome === requestedOutcome &&
941
          JSON.stringify(candidate.contract.successCriteria) === JSON.stringify(successCriteria) &&
942
          JSON.stringify(candidate.contract.allowedActions) ===
943
            JSON.stringify(input.allowedActions) &&
944
          JSON.stringify(candidate.contract.prohibitedActions) ===
945
            JSON.stringify(input.prohibitedActions) &&
946
          JSON.stringify(candidate.contract.allowedData) === JSON.stringify(input.allowedData) &&
947
          JSON.stringify(candidate.contract.prohibitedData) ===
948
            JSON.stringify(input.prohibitedData) &&
949
          JSON.stringify(candidate.contract.evidenceRequirements) ===
950
            JSON.stringify(input.evidenceRequirements) &&
951
          candidate.contract.deadline === input.deadline,
952
      );
953
      const now = new Date();
954
      const interactionId = existing?.interactionId ?? crypto.randomUUID();
955
      const contract = {
956
        protocolVersion: '0.1' as const,
957
        interactionId,
958
        purpose,
959
        parties: [binding.agent.agentId, responderCard.agentId],
960
        taskCategory,
961
        requestedOutcome,
962
        successCriteria,
963
        allowedActions: input.allowedActions,
964
        prohibitedActions: input.prohibitedActions,
965
        allowedData: input.allowedData,
966
        prohibitedData: input.prohibitedData,
967
        ...(input.deadline ? { deadline: input.deadline } : {}),
968
        evidenceRequirements: input.evidenceRequirements,
969
        delegationRules: ['explicit_contract_scope'],
970
        humanApprovalRequirements: [],
971
        factCheckingPolicy: 'important_claims',
972
        mediationPolicy: 'mutual_consent' as const,
973
        retentionDays: 30,
974
        completionConditions: successCriteria,
975
        cancellationConditions: ['either_party_before_completion'],
976
        signatures: {},
977
      };
978
      const termsHash = existing?.termsHash ?? canonicalHash(contract);
979
      const createdAt = now.toISOString();
980
      const interaction: FederatedInteraction = {
981
        protocolVersion: '0.1',
982
        interactionId,
983
        initiatorAgentId: binding.agent.agentId,
984
        responderAgentId: responderCard.agentId,
985
        status: 'pending',
986
        contract,
987
        termsHash,
988
        acceptances: {
989
          [binding.agent.agentId]: {
990
            agentId: binding.agent.agentId,
991
            method: 'oauth_installation',
992
            termsHash,
993
            acceptedAt: createdAt,
994
          },
995
        },
996
        contractRevision: 1,
997
        contractRevisions: [],
998
        ...(initiatorCard.transports[0] ? { initiatorTransport: initiatorCard.transports[0] } : {}),
999
        responderTransport,
1000
        createdAt,
1001
        updatedAt: createdAt,
1002
        expiresAt: new Date(now.getTime() + input.expiresInMinutes * 60_000).toISOString(),
1003
      };
1004
      const stored =
1005
        existing ??
1006
        (await agentDirectory.createFederatedInteraction(connection.operatorId, interaction));
1007
      const session =
1008
        stored.status === 'active'
1009
          ? await agentDirectory.getLiveSession(
1010
              connection.operatorId,
1011
              stored.interactionId,
1012
              binding.agent.agentId,
1013
            )
1014
          : undefined;
1015
      return text({
1016
        ready: stored.status === 'active',
1017
        session,
1018
        interaction: stored,
1019
        a2a: {
1020
          endpoint: session?.peer.endpoint,
1021
          bearerToken: session?.peer.bearerToken,
1022
          protocolBinding: stored.responderTransport.protocolBinding,
1023
          extensions: [DEFAULT_EXTENSION_URI],
1024
          metadata: {
1025
            [DEFAULT_EXTENSION_URI]: {
1026
              interactionId: stored.interactionId,
1027
              termsHash: stored.termsHash,
1028
              initiatorAgentId: stored.initiatorAgentId,
1029
              responderAgentId: stored.responderAgentId,
1030
            },
1031
          },
1032
          requestTemplate: {
1033
            method: 'message/send',
1034
            headers: { 'A2A-Extensions': DEFAULT_EXTENSION_URI },
1035
            message: {
1036
              role: 'user',
1037
              parts: [{ kind: 'text', text: task }],
1038
              metadata: {
1039
                [DEFAULT_EXTENSION_URI]: {
1040
                  interactionId: stored.interactionId,
1041
                  termsHash: stored.termsHash,
1042
                  initiatorAgentId: stored.initiatorAgentId,
1043
                  responderAgentId: stored.responderAgentId,
1044
                },
1045
              },
1046
            },
1047
          },
1048
        },
1049
        next:
1050
          stored.status === 'active'
1051
            ? 'Both live runtimes accepted. Send the A2A request directly to session.peer.endpoint; OpenClasp is not in the message path.'
1052
            : `The task needs responder approval. OpenClasp will expose it at ${process.env.OPENCLASP_DASHBOARD_URL ?? 'https://openclasp.vercel.app'}/dashboard; retry this interaction after approval.`,
1053
      });
1054
    },
1055
  );
1056
  server.registerTool(
1057
    OPENCLASP_TOOL_NAMES[25],
1058
    {
1059
      title: 'List shared invitations',
1060
      description: 'List incoming and outgoing cross-account interaction invitations and status.',
1061
      inputSchema: z.object({}),
1062
      annotations: READ_ONLY_TOOL,
1063
    },
1064
    async (_input, context) => {
1065
      if (!agentDirectory) throw new Error('Federated agent connections are not configured');
1066
      await requireBoundAgent(context);
1067
      const connection = installationContext(context);
1068
      return text(await agentDirectory.listFederatedInteractions(connection.operatorId));
1069
    },
1070
  );
1071
  server.registerTool(
1072
    OPENCLASP_TOOL_NAMES[26],
1073
    {
1074
      title: 'Respond to agent invitation',
1075
      description: 'Accept or reject an invitation as the agent bound to this MCP installation.',
1076
      inputSchema: z.object({
1077
        interactionId: z.string().uuid(),
1078
        decision: z.enum(['accept', 'reject']),
1079
      }),
1080
      annotations: WRITE_TOOL,
1081
    },
1082
    async (input, context) => {
1083
      if (!agentDirectory) throw new Error('Federated agent connections are not configured');
1084
      const binding = await requireBoundAgent(context);
1085
      if (!binding) throw new Error('A hosted, bound MCP installation is required');
1086
      const connection = installationContext(context);
1087
      return text(
1088
        await agentDirectory.respondToFederatedInteraction(
1089
          connection.operatorId,
1090
          input.interactionId,
1091
          binding.agent.agentId,
1092
          input.decision,
1093
          'oauth_installation',
1094
        ),
1095
      );
1096
    },
1097
  );
1098
  server.registerTool(
1099
    OPENCLASP_TOOL_NAMES[27],
1100
    {
1101
      title: 'Get shared interaction',
1102
      description: 'Get the canonical contract and bilateral acceptance state for one interaction.',
1103
      inputSchema: z.object({ interactionId: z.string().uuid() }),
1104
      annotations: READ_ONLY_TOOL,
1105
    },
1106
    async (input, context) => {
1107
      if (!agentDirectory) throw new Error('Federated agent connections are not configured');
1108
      await requireBoundAgent(context);
1109
      const connection = installationContext(context);
1110
      const value = await agentDirectory.getFederatedInteraction(
1111
        connection.operatorId,
1112
        input.interactionId,
1113
      );
1114
      if (!value) throw new Error('Interaction not found');
1115
      return text(value);
1116
    },
1117
  );
1118
  server.registerTool(
1119
    OPENCLASP_TOOL_NAMES[28],
1120
    {
1121
      title: 'Get direct live session',
1122
      description:
1123
        'Get the peer A2A endpoint and short-lived credential for a brokered live session.',
1124
      inputSchema: z.object({ interactionId: z.string().uuid() }),
1125
      annotations: READ_ONLY_TOOL,
1126
    },
1127
    async (input, context) => {
1128
      if (!agentDirectory) throw new Error('Live agent sessions are not configured');
1129
      const binding = await requireBoundAgent(context);
1130
      if (!binding) throw new Error('A bound MCP installation is required');
1131
      const connection = installationContext(context);
1132
      return text(
1133
        await agentDirectory.getLiveSession(
1134
          connection.operatorId,
1135
          input.interactionId,
1136
          binding.agent.agentId,
1137
        ),
1138
      );
1139
    },
1140
  );
1141
  server.registerTool(
1142
    OPENCLASP_TOOL_NAMES[29],
1143
    {
1144
      title: 'Record structured session event',
1145
      description:
1146
        'Record signed session metadata, hashes, evidence, corrections, or results without uploading raw messages.',
1147
      inputSchema: LiveSessionEventInputSchema,
1148
      annotations: WRITE_TOOL,
1149
    },
1150
    async (input, context) => {
1151
      if (!agentDirectory) throw new Error('Live agent sessions are not configured');
1152
      const binding = await requireBoundAgent(context);
1153
      if (!binding) throw new Error('A bound MCP installation is required');
1154
      const connection = installationContext(context);
1155
      const session = await agentDirectory.getLiveSession(
1156
        connection.operatorId,
1157
        input.interactionId,
1158
        binding.agent.agentId,
1159
      );
1160
      return text(
1161
        await agentDirectory.recordLiveSessionEvent(session.reporting.bearerToken, {
1162
          ...input,
1163
          eventId: crypto.randomUUID(),
1164
          agentId: binding.agent.agentId,
1165
        }),
1166
      );
1167
    },
1168
  );
1169
  server.registerTool(
1170
    OPENCLASP_TOOL_NAMES[30],
1171
    {
1172
      title: 'Complete live session',
1173
      description:
1174
        'Finalize this participant’s side of a live session. Records the terminal event and submits a structured completion report, which triggers the persistent peer’s private finalization. Optional assessment and feedback improve the resulting reliability history.',
1175
      inputSchema: z
1176
        .object({
1177
          interactionId: z.string().uuid(),
1178
          sequence: z.number().int().nonnegative(),
1179
          outcome: z.enum(['success', 'failure', 'partial']),
1180
          evidenceReferences: z.array(z.string().min(1).max(2048)).max(100).default([]),
1181
          details: LiveSessionEventSchema.shape.details,
1182
          assessment: z
1183
            .object({
1184
              summary: z.string().min(1).max(2000),
1185
              criteria: z
1186
                .array(
1187
                  z
1188
                    .object({
1189
                      criterion: z.string().min(1).max(1000),
1190
                      status: z.enum(['met', 'partially_met', 'missed', 'unknown']),
1191
                      explanation: z.string().max(1000).optional(),
1192
                      evidenceReferences: z.array(z.string().min(1).max(2048)).max(50).default([]),
1193
                    })
1194
                    .strict(),
1195
                )
1196
                .max(100)
1197
                .default([]),
1198
              deliverables: z.array(z.string().min(1).max(1000)).max(100).default([]),
1199
              actionsTaken: z.array(z.string().min(1).max(1000)).max(100).default([]),
1200
              blockers: z.array(z.string().min(1).max(1000)).max(100).default([]),
1201
              corrections: z.array(z.string().min(1).max(1000)).max(100).default([]),
1202
              confidence: z.number().min(0).max(1),
1203
            })
1204
            .strict()
1205
            .optional(),
1206
          feedback: z
1207
            .object({
1208
              ratings: z
1209
                .object({
1210
                  overall_satisfaction: z.number().min(0).max(1),
1211
                  outcome_satisfaction: z.number().min(0).max(1),
1212
                  communication: z.number().min(0).max(1),
1213
                  timeliness: z.number().min(0).max(1),
1214
                  scope_adherence: z.number().min(0).max(1),
1215
                  evidence_quality: z.number().min(0).max(1),
1216
                  correction_handling: z.number().min(0).max(1),
1217
                  reliability: z.number().min(0).max(1),
1218
                })
1219
                .strict(),
1220
              wouldWorkAgain: z.enum(['yes', 'no', 'unsure']),
1221
              reasonCodes: z.array(z.string().min(1).max(128)).max(32).default([]),
1222
              privateComment: z.string().max(1000).optional(),
1223
              confidence: z.number().min(0).max(1),
1224
            })
1225
            .strict()
1226
            .optional(),
1227
        })
1228
        .strict(),
1229
      annotations: WRITE_TOOL,
1230
    },
1231
    async (input, context) => {
1232
      if (!agentDirectory?.submitCompletionReport)
1233
        throw new Error('Live agent session completion is not configured');
1234
      const binding = await requireBoundAgent(context);
1235
      if (!binding) throw new Error('A bound MCP installation is required');
1236
      const connection = installationContext(context);
1237
      const session = await agentDirectory.getLiveSession(
1238
        connection.operatorId,
1239
        input.interactionId,
1240
        binding.agent.agentId,
1241
      );
1242
      const interaction = await agentDirectory.getFederatedInteraction(
1243
        connection.operatorId,
1244
        input.interactionId,
1245
      );
1246
      if (!interaction || !interaction.contract.parties.includes(binding.agent.agentId))
1247
        throw new Error('Bound agent is not an interaction participant');
1248
      const counterpartyAgentId =
1249
        interaction.initiatorAgentId === binding.agent.agentId
1250
          ? interaction.responderAgentId
1251
          : interaction.initiatorAgentId;
1252
      const occurredAt = new Date().toISOString();
1253
      const event = await agentDirectory.recordLiveSessionEvent(session.reporting.bearerToken, {
1254
        eventId: crypto.randomUUID(),
1255
        interactionId: input.interactionId,
1256
        agentId: binding.agent.agentId,
1257
        sequence: input.sequence,
1258
        type: input.outcome === 'failure' ? 'session_failed' : 'session_completed',
1259
        occurredAt,
1260
        evidenceReferences: input.evidenceReferences,
1261
        outcome: input.outcome,
1262
        details: input.details,
1263
      });
1264
      const suppliedCriteria = input.assessment?.criteria ?? [];
1265
      const report = InteractionCompletionReportSchema.parse({
1266
        reportId: crypto.randomUUID(),
1267
        interactionId: input.interactionId,
1268
        contractHash: interaction.termsHash,
1269
        reportingAgentId: binding.agent.agentId,
1270
        counterpartyAgentId,
1271
        agentVersion: binding.agent.agentVersion,
1272
        outcome: input.outcome,
1273
        summary:
1274
          input.assessment?.summary ??
1275
          `The agent reported this live session as ${input.outcome}. Detailed assessment was not supplied by the MCP client.`,
1276
        requestedOutcome: interaction.contract.requestedOutcome,
1277
        criteria: interaction.contract.successCriteria.map(
1278
          (criterion) =>
1279
            suppliedCriteria.find((candidate) => candidate.criterion === criterion) ?? {
1280
              criterion,
1281
              status: 'unknown',
1282
              evidenceReferences: [],
1283
            },
1284
        ),
1285
        deliverables: input.assessment?.deliverables ?? [],
1286
        actionsTaken: input.assessment?.actionsTaken ?? [],
1287
        blockers: input.assessment?.blockers ?? [],
1288
        scopeChanges: [],
1289
        corrections: input.assessment?.corrections ?? [],
1290
        evidenceReferences: input.evidenceReferences,
1291
        ...(session.activatedAt ? { startedAt: session.activatedAt } : {}),
1292
        completedAt: occurredAt,
1293
        confidence: input.assessment?.confidence ?? 0.25,
1294
        dataSharingMode: 'structured_only',
1295
      });
1296
      const submissionMethod =
1297
        connection.credentialType === 'agent_access_token'
1298
          ? 'agent_access_token'
1299
          : 'oauth_installation';
1300
      const completion = (await agentDirectory.submitCompletionReport(
1301
        connection.operatorId,
1302
        binding.agent.agentId,
1303
        report,
1304
        submissionMethod,
1305
      )) as { feedbackRequest?: { requestId?: string; status?: string } };
1306
      let feedback: unknown;
1307
      if (
1308
        input.feedback &&
1309
        completion.feedbackRequest?.requestId &&
1310
        completion.feedbackRequest.status !== 'submitted' &&
1311
        agentDirectory.submitInteractionFeedback
1312
      ) {
1313
        feedback = await agentDirectory.submitInteractionFeedback(
1314
          connection.operatorId,
1315
          binding.agent.agentId,
1316
          InteractionFeedbackSchema.parse({
1317
            feedbackId: crypto.randomUUID(),
1318
            requestId: completion.feedbackRequest.requestId,
1319
            interactionId: input.interactionId,
1320
            reviewerAgentId: binding.agent.agentId,
1321
            subjectAgentId: counterpartyAgentId,
1322
            reviewerAgentVersion: binding.agent.agentVersion,
1323
            ratings: input.feedback.ratings,
1324
            wouldWorkAgain: input.feedback.wouldWorkAgain,
1325
            reasonCodes: input.feedback.reasonCodes,
1326
            ...(input.feedback.privateComment
1327
              ? { privateComment: input.feedback.privateComment }
1328
              : {}),
1329
            evidenceReferences: input.evidenceReferences,
1330
            confidence: input.feedback.confidence,
1331
            submittedAt: occurredAt,
1332
          }),
1333
          submissionMethod,
1334
        );
1335
      }
1336
      return text({ event, completion, ...(feedback ? { feedback } : {}) });
1337
    },
1338
  );
1339
  server.registerTool(
1340
    OPENCLASP_TOOL_NAMES[31],
1341
    {
1342
      title: 'Heartbeat agent presence',
1343
      description:
1344
        'Mark this authenticated agent online. Online means activity within the last two minutes.',
1345
      inputSchema: z.object({}),
1346
      annotations: { ...WRITE_TOOL, idempotentHint: true },
1347
    },
1348
    async (_input, context) => {
1349
      if (!agentDirectory) throw new Error('Hosted agent presence is not configured');
1350
      if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
1351
      const { connection, binding } = await resolveAuthenticatedInstallation(context);
1352
      if (binding.status !== 'connected')
1353
        throw new Error('Call openclasp_setup and obtain owner confirmation first');
1354
      return text({
1355
        agentId: binding.agent.agentId,
1356
        presence: await agentDirectory.touchAgentPresence(
1357
          connection.operatorId,
1358
          binding.agent.agentId,
1359
        ),
1360
      });
1361
    },
1362
  );
1363
  server.registerTool(
1364
    OPENCLASP_TOOL_NAMES[41],
1365
    {
1366
      title: 'Checkpoint interaction progress',
1367
      description:
1368
        'Send a compact structured progress pulse without raw conversation text. Use after roughly five meaningful exchanges, or immediately when blocked, drifting from scope, or ready to finish.',
1369
      inputSchema: z
1370
        .object({
1371
          interactionId: z.string().uuid(),
1372
          sequence: z.number().int().nonnegative(),
1373
          checkpoint: ProgressCheckpointSchema,
1374
        })
1375
        .strict(),
1376
      annotations: WRITE_TOOL,
1377
    },
1378
    async (input, context) => {
1379
      if (!agentDirectory) throw new Error('Live agent sessions are not configured');
1380
      const binding = await requireBoundAgent(context);
1381
      if (!binding) throw new Error('A bound MCP installation is required');
1382
      const connection = installationContext(context);
1383
      const session = await agentDirectory.getLiveSession(
1384
        connection.operatorId,
1385
        input.interactionId,
1386
        binding.agent.agentId,
1387
      );
1388
      const result = await agentDirectory.recordLiveSessionEvent(session.reporting.bearerToken, {
1389
        eventId: crypto.randomUUID(),
1390
        interactionId: input.interactionId,
1391
        agentId: binding.agent.agentId,
1392
        sequence: input.sequence,
1393
        type: 'progress_checkpoint',
1394
        occurredAt: new Date().toISOString(),
1395
        evidenceReferences: [],
1396
        checkpoint: input.checkpoint,
1397
        details: {
1398
          labels: [`state:${input.checkpoint.state}`, `topic:${input.checkpoint.topicStatus}`],
1399
          metrics: { progress: input.checkpoint.progress, confidence: input.checkpoint.confidence },
1400
          flags: { needsHuman: input.checkpoint.needsHuman },
1401
        },
1402
      });
1403
      return text({
1404
        checkpoint: result,
1405
        ...(input.checkpoint.state === 'done' || input.checkpoint.state === 'ready_to_finalize'
1406
          ? { nextAction: 'Call openclasp_complete_live_session now.' }
1407
          : {}),
1408
      });
1409
    },
1410
  );
1411
  server.registerTool(
1412
    OPENCLASP_TOOL_NAMES[38],
1413
    {
1414
      title: 'Submit structured completion report',
1415
      description:
1416
        'Submit this agent’s structured outcome against the signed interaction contract. Raw messages and transcripts are rejected.',
1417
      inputSchema: z
1418
        .object({
1419
          interactionId: z.string().uuid(),
1420
          outcome: z.enum(['success', 'partial', 'failure', 'cancelled']),
1421
          summary: z.string().min(1).max(2000),
1422
          criteria: z
1423
            .array(
1424
              z
1425
                .object({
1426
                  criterion: z.string().min(1).max(1000),
1427
                  status: z.enum(['met', 'partially_met', 'missed', 'unknown']),
1428
                  explanation: z.string().max(1000).optional(),
1429
                  evidenceReferences: z.array(z.string().min(1).max(2048)).max(50).default([]),
1430
                })
1431
                .strict(),
1432
            )
1433
            .max(100),
1434
          deliverables: z.array(z.string().min(1).max(1000)).max(100).default([]),
1435
          actionsTaken: z.array(z.string().min(1).max(1000)).max(100).default([]),
1436
          blockers: z.array(z.string().min(1).max(1000)).max(100).default([]),
1437
          scopeChanges: z.array(z.string().min(1).max(1000)).max(100).default([]),
1438
          corrections: z.array(z.string().min(1).max(1000)).max(100).default([]),
1439
          evidenceReferences: z.array(z.string().min(1).max(2048)).max(100).default([]),
1440
          startedAt: z.string().datetime().optional(),
1441
          confidence: z.number().min(0).max(1),
1442
        })
1443
        .strict(),
1444
      annotations: WRITE_TOOL,
1445
    },
1446
    async (input, context) => {
1447
      if (!agentDirectory?.submitCompletionReport)
1448
        throw new Error('Interaction completion is not configured');
1449
      const binding = await requireBoundAgent(context);
1450
      if (!binding) throw new Error('A bound MCP installation is required');
1451
      const connection = installationContext(context);
1452
      const interaction = await agentDirectory.getFederatedInteraction(
1453
        connection.operatorId,
1454
        input.interactionId,
1455
      );
1456
      if (!interaction) throw new Error('Interaction not found');
1457
      const counterpartyAgentId =
1458
        interaction.initiatorAgentId === binding.agent.agentId
1459
          ? interaction.responderAgentId
1460
          : interaction.initiatorAgentId;
1461
      if (!interaction.contract.parties.includes(binding.agent.agentId))
1462
        throw new Error('Bound agent is not an interaction participant');
1463
      const report = InteractionCompletionReportSchema.parse({
1464
        reportId: crypto.randomUUID(),
1465
        interactionId: input.interactionId,
1466
        contractHash: interaction.termsHash,
1467
        reportingAgentId: binding.agent.agentId,
1468
        counterpartyAgentId,
1469
        agentVersion: binding.agent.agentVersion,
1470
        outcome: input.outcome,
1471
        summary: input.summary,
1472
        requestedOutcome: interaction.contract.requestedOutcome,
1473
        criteria: input.criteria,
1474
        deliverables: input.deliverables,
1475
        actionsTaken: input.actionsTaken,
1476
        blockers: input.blockers,
1477
        scopeChanges: input.scopeChanges,
1478
        corrections: input.corrections,
1479
        evidenceReferences: input.evidenceReferences,
1480
        ...(input.startedAt ? { startedAt: input.startedAt } : {}),
1481
        completedAt: new Date().toISOString(),
1482
        confidence: input.confidence,
1483
        dataSharingMode: 'structured_only',
1484
      });
1485
      return text(
1486
        await agentDirectory.submitCompletionReport(
1487
          connection.operatorId,
1488
          binding.agent.agentId,
1489
          report,
1490
          connection.credentialType === 'agent_access_token'
1491
            ? 'agent_access_token'
1492
            : 'oauth_installation',
1493
        ),
1494
      );
1495
    },
1496
  );
1497
  server.registerTool(
1498
    OPENCLASP_TOOL_NAMES[39],
1499
    {
1500
      title: 'List feedback requests',
1501
      description:
1502
        'List this agent’s pending and completed bilateral feedback requests. Peer feedback remains concealed until both respond or the timeout expires.',
1503
      inputSchema: z.object({}),
1504
      annotations: READ_ONLY_TOOL,
1505
    },
1506
    async (_input, context) => {
1507
      if (!agentDirectory?.listFeedbackRequests)
1508
        throw new Error('Interaction feedback is not configured');
1509
      const binding = await requireBoundAgent(context);
1510
      if (!binding) throw new Error('A bound MCP installation is required');
1511
      const connection = installationContext(context);
1512
      return text(
1513
        await agentDirectory.listFeedbackRequests(connection.operatorId, binding.agent.agentId),
1514
      );
1515
    },
1516
  );
1517
  server.registerTool(
1518
    OPENCLASP_TOOL_NAMES[40],
1519
    {
1520
      title: 'Submit bilateral interaction feedback',
1521
      description:
1522
        'Rate the counterparty after an interaction. The private comment is never included in the shared aggregate conclusion.',
1523
      inputSchema: z
1524
        .object({
1525
          requestId: z.string().uuid(),
1526
          interactionId: z.string().uuid(),
1527
          ratings: z
1528
            .object({
1529
              overall_satisfaction: z.number().min(0).max(1),
1530
              outcome_satisfaction: z.number().min(0).max(1),
1531
              communication: z.number().min(0).max(1),
1532
              timeliness: z.number().min(0).max(1),
1533
              scope_adherence: z.number().min(0).max(1),
1534
              evidence_quality: z.number().min(0).max(1),
1535
              correction_handling: z.number().min(0).max(1),
1536
              reliability: z.number().min(0).max(1),
1537
            })
1538
            .strict(),
1539
          wouldWorkAgain: z.enum(['yes', 'no', 'unsure']),
1540
          reasonCodes: z.array(z.string().min(1).max(128)).max(32).default([]),
1541
          privateComment: z.string().max(1000).optional(),
1542
          evidenceReferences: z.array(z.string().min(1).max(2048)).max(50).default([]),
1543
          confidence: z.number().min(0).max(1),
1544
        })
1545
        .strict(),
1546
      annotations: WRITE_TOOL,
1547
    },
1548
    async (input, context) => {
1549
      if (!agentDirectory?.submitInteractionFeedback)
1550
        throw new Error('Interaction feedback is not configured');
1551
      const binding = await requireBoundAgent(context);
1552
      if (!binding) throw new Error('A bound MCP installation is required');
1553
      const connection = installationContext(context);
1554
      const interaction = await agentDirectory.getFederatedInteraction(
1555
        connection.operatorId,
1556
        input.interactionId,
1557
      );
1558
      if (!interaction) throw new Error('Interaction not found');
1559
      const subjectAgentId =
1560
        interaction.initiatorAgentId === binding.agent.agentId
1561
          ? interaction.responderAgentId
1562
          : interaction.initiatorAgentId;
1563
      if (!interaction.contract.parties.includes(binding.agent.agentId))
1564
        throw new Error('Bound agent is not an interaction participant');
1565
      const feedback = InteractionFeedbackSchema.parse({
1566
        feedbackId: crypto.randomUUID(),
1567
        requestId: input.requestId,
1568
        interactionId: input.interactionId,
1569
        reviewerAgentId: binding.agent.agentId,
1570
        subjectAgentId,
1571
        reviewerAgentVersion: binding.agent.agentVersion,
1572
        ratings: input.ratings,
1573
        wouldWorkAgain: input.wouldWorkAgain,
1574
        reasonCodes: input.reasonCodes,
1575
        ...(input.privateComment ? { privateComment: input.privateComment } : {}),
1576
        evidenceReferences: input.evidenceReferences,
1577
        confidence: input.confidence,
1578
        submittedAt: new Date().toISOString(),
1579
      });
1580
      return text(
1581
        await agentDirectory.submitInteractionFeedback(
1582
          connection.operatorId,
1583
          binding.agent.agentId,
1584
          feedback,
1585
          connection.credentialType === 'agent_access_token'
1586
            ? 'agent_access_token'
1587
            : 'oauth_installation',
1588
        ),
1589
      );
1590
    },
1591
  );
1592
  server.registerTool(
1593
    OPENCLASP_TOOL_NAMES[32],
1594
    {
1595
      title: 'List temporary chat threads',
1596
      description:
1597
        'List hosted threads for this temporary chat identity. Message bodies are encrypted at rest and never mixed with direct A2A sessions.',
1598
      inputSchema: z.object({}),
1599
      annotations: READ_ONLY_TOOL,
1600
    },
1601
    async (_input, context) => {
1602
      if (!agentDirectory) throw new Error('Temporary chat history is not configured');
1603
      const binding = await requireBoundAgent(context);
1604
      if (!binding) throw new Error('A bound MCP installation is required');
1605
      const connection = installationContext(context);
1606
      return text(
1607
        await agentDirectory.listHostedThreads(connection.operatorId, binding.agent.agentId),
1608
      );
1609
    },
1610
  );
1611
  server.registerTool(
1612
    OPENCLASP_TOOL_NAMES[33],
1613
    {
1614
      title: 'Get temporary chat thread',
1615
      description:
1616
        'Read one hosted temporary-agent thread plus private, task-specific counterparty insights.',
1617
      inputSchema: z.object({ threadId: z.string().uuid() }),
1618
      annotations: READ_ONLY_TOOL,
1619
    },
1620
    async (input, context) => {
1621
      if (!agentDirectory) throw new Error('Temporary chat history is not configured');
1622
      const binding = await requireBoundAgent(context);
1623
      if (!binding) throw new Error('A bound MCP installation is required');
1624
      const connection = installationContext(context);
1625
      return text(
1626
        await agentDirectory.getHostedThread(
1627
          connection.operatorId,
1628
          binding.agent.agentId,
1629
          input.threadId,
1630
        ),
1631
      );
1632
    },
1633
  );
1634
  server.registerTool(
1635
    OPENCLASP_TOOL_NAMES[34],
1636
    {
1637
      title: 'Send from temporary chat',
1638
      description:
1639
        'Send text from this temporary identity to the persistent peer over A2A. OpenClasp processes and encrypts this hosted-mode message.',
1640
      inputSchema: z.object({
1641
        interactionId: z.string().uuid(),
1642
        content: z.string().trim().min(1).max(20_000),
1643
      }),
1644
      annotations: WRITE_TOOL,
1645
    },
1646
    async (input, context) => {
1647
      if (!agentDirectory) throw new Error('Temporary chat delivery is not configured');
1648
      const binding = await requireBoundAgent(context);
1649
      if (!binding) throw new Error('A bound MCP installation is required');
1650
      const connection = installationContext(context);
1651
      return text(
1652
        await agentDirectory.sendTemporaryMessage(
1653
          connection.operatorId,
1654
          binding.agent.agentId,
1655
          input.interactionId,
1656
          input.content,
1657
        ),
1658
      );
1659
    },
1660
  );
1661
  server.registerTool(
1662
    OPENCLASP_TOOL_NAMES[35],
1663
    {
1664
      title: 'Reply to temporary chat thread',
1665
      description: 'Reply to the persistent peer in an existing hosted temporary-agent thread.',
1666
      inputSchema: z.object({
1667
        threadId: z.string().uuid(),
1668
        content: z.string().trim().min(1).max(20_000),
1669
      }),
1670
      annotations: WRITE_TOOL,
1671
    },
1672
    async (input, context) => {
1673
      if (!agentDirectory) throw new Error('Temporary chat delivery is not configured');
1674
      const binding = await requireBoundAgent(context);
1675
      if (!binding) throw new Error('A bound MCP installation is required');
1676
      const connection = installationContext(context);
1677
      const thread = await agentDirectory.getHostedThread(
1678
        connection.operatorId,
1679
        binding.agent.agentId,
1680
        input.threadId,
1681
      );
1682
      return text(
1683
        await agentDirectory.sendTemporaryMessage(
1684
          connection.operatorId,
1685
          binding.agent.agentId,
1686
          thread.thread.interactionId,
1687
          input.content,
1688
        ),
1689
      );
1690
    },
1691
  );
1692
  server.registerTool(
1693
    OPENCLASP_TOOL_NAMES[36],
1694
    {
1695
      title: 'Mark temporary thread read',
1696
      description: 'Mark inbound messages in one hosted temporary-agent thread as read.',
1697
      inputSchema: z.object({ threadId: z.string().uuid() }),
1698
      annotations: { ...WRITE_TOOL, idempotentHint: true },
1699
    },
1700
    async (input, context) => {
1701
      if (!agentDirectory) throw new Error('Temporary chat history is not configured');
1702
      const binding = await requireBoundAgent(context);
1703
      if (!binding) throw new Error('A bound MCP installation is required');
1704
      const connection = installationContext(context);
1705
      return text(
1706
        await agentDirectory.markHostedThreadRead(
1707
          connection.operatorId,
1708
          binding.agent.agentId,
1709
          input.threadId,
1710
        ),
1711
      );
1712
    },
1713
  );
1714
  server.registerTool(
1715
    OPENCLASP_TOOL_NAMES[37],
1716
    {
1717
      title: 'Close temporary chat thread',
1718
      description: 'Close a hosted temporary-agent thread. New messages will be rejected.',
1719
      inputSchema: z.object({ threadId: z.string().uuid() }),
1720
      annotations: WRITE_TOOL,
1721
    },
1722
    async (input, context) => {
1723
      if (!agentDirectory) throw new Error('Temporary chat history is not configured');
1724
      const binding = await requireBoundAgent(context);
1725
      if (!binding) throw new Error('A bound MCP installation is required');
1726
      const connection = installationContext(context);
1727
      return text(
1728
        await agentDirectory.closeHostedThread(
1729
          connection.operatorId,
1730
          binding.agent.agentId,
1731
          input.threadId,
1732
        ),
1733
      );
1734
    },
1735
  );
1736
  server.registerTool(
1737
    OPENCLASP_TOOL_NAMES[42],
1738
    {
1739
      title: 'Resolve agent reference',
1740
      description:
1741
        'Resolve an OpenClasp profile URL, card URL, A2A card URL, public slug, or agent ID to one verified published agent.',
1742
      inputSchema: z.object({ reference: z.string().trim().min(1).max(2048) }),
1743
      annotations: { ...READ_ONLY_TOOL, openWorldHint: true },
1744
    },
1745
    async (input, context) => {
1746
      await requireBoundAgent(context);
1747
      if (!agentDirectory) throw new Error('The shared agent directory is not configured');
1748
      const resolved = agentDirectory.resolveAgentReference
1749
        ? await agentDirectory.resolveAgentReference(input.reference)
1750
        : await agentDirectory.getPublishedAgent(input.reference);
1751
      if (!resolved) throw new Error('Published agent reference was not found');
1752
      return text(resolved);
1753
    },
1754
  );
1755
  server.registerTool(
1756
    OPENCLASP_TOOL_NAMES[43],
1757
    {
1758
      title: 'Propose contract revision',
1759
      description:
1760
        'Propose or counter complete structured terms for a pending interaction, or propose an amendment to an active interaction. The proposal is bound to the authenticated agent.',
1761
      inputSchema: z.object({
1762
        interactionId: z.string().uuid(),
1763
        expectedTermsHash: z.string().min(1).optional(),
1764
        contract: InteractionContractSchema,
1765
      }),
1766
      annotations: WRITE_TOOL,
1767
    },
1768
    async (input, context) => {
1769
      if (!agentDirectory?.proposeContractRevision)
1770
        throw new Error('Contract negotiation is not configured');
1771
      const binding = await requireBoundAgent(context);
1772
      if (!binding) throw new Error('A bound MCP installation is required');
1773
      if (input.contract.interactionId !== input.interactionId)
1774
        throw new Error('Contract interaction ID does not match');
1775
      const connection = installationContext(context);
1776
      return text(
1777
        await agentDirectory.proposeContractRevision(
1778
          connection.operatorId,
1779
          input.interactionId,
1780
          binding.agent.agentId,
1781
          input.contract,
1782
          input.expectedTermsHash,
1783
          'oauth_installation',
1784
        ),
1785
      );
1786
    },
1787
  );
1788
  server.registerTool(
1789
    OPENCLASP_TOOL_NAMES[44],
1790
    {
1791
      title: 'Respond to contract revision',
1792
      description:
1793
        'Accept or reject the current contract proposal or amendment as the authenticated agent. Bilateral acceptance creates a platform-attested revision.',
1794
      inputSchema: z.object({
1795
        interactionId: z.string().uuid(),
1796
        revisionId: z.string().uuid(),
1797
        decision: z.enum(['accept', 'reject']),
1798
      }),
1799
      annotations: WRITE_TOOL,
1800
    },
1801
    async (input, context) => {
1802
      if (!agentDirectory?.respondToContractRevision)
1803
        throw new Error('Contract negotiation is not configured');
1804
      const binding = await requireBoundAgent(context);
1805
      if (!binding) throw new Error('A bound MCP installation is required');
1806
      const connection = installationContext(context);
1807
      return text(
1808
        await agentDirectory.respondToContractRevision(
1809
          connection.operatorId,
1810
          input.interactionId,
1811
          binding.agent.agentId,
1812
          input.revisionId,
1813
          input.decision,
1814
          'oauth_installation',
1815
        ),
1816
      );
1817
    },
1818
  );
1819
  return server;
1820
}