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

Viewing files at commit 95b0e11125d2Commit detailsView latest

packages/mcp-server/src/server.ts 2242 lines · 88.8 KB · typescript
1
import { McpServer } from '@modelcontextprotocol/server';
2
import { z } from 'zod';
3
import { createIdentity, TrustEngine, UnavailableFactCheckProvider } from '../../core/src/index.js';
4
import {
5
  AgentIdentitySchema,
6
  AssuranceProbeAnswerSchema,
7
  AssuranceProbeResponseSchema,
8
  DEFAULT_EXTENSION_URI,
9
  DelegationCredentialSchema,
10
  FeedbackSchema,
11
  InteractionContractSchema,
12
  InteractionCompletionReportSchema,
13
  InteractionFeedbackSchema,
14
  InteractionEventSchema,
15
  LiveSessionEventSchema,
16
  ProgressCheckpointSchema,
17
  ReceiptSchema,
18
  ShieldCaseSchema,
19
  ShieldOutcomeSchema,
20
  TrustEnvelopeSchema,
21
  canonicalHash,
22
  type FederatedInteraction,
23
  type PublicAgentCard,
24
} from '../../protocol/src/index.js';
25
import {
26
  requestAgentSetup,
27
  resolveInstallation,
28
  updateAgentProfile,
29
  type OnboardingStore,
30
} from '../../persistence/src/onboarding.js';
31
import { buildPublicAgentCard } from '../../persistence/src/hosted.js';
32
import { generateAssuranceProbePlan } from './assurance-ai.js';
33
import {
34
  createShieldCase,
35
  consultShield,
36
  ShieldCaseInputSchema,
37
  ShieldConsultInputSchema,
38
} from './shield-agent.js';
39
40
export const OPENCLASP_TOOL_NAMES = [
41
  'openclasp_create_identity',
42
  'openclasp_register_agent',
43
  'openclasp_get_profile',
44
  'openclasp_assess_counterparty',
45
  'openclasp_begin_interaction',
46
  'openclasp_record_event',
47
  'openclasp_check_claim',
48
  'openclasp_validate_commitment',
49
  'openclasp_suggest_resolution',
50
  'openclasp_complete_interaction',
51
  'openclasp_submit_feedback',
52
  'openclasp_raise_dispute',
53
  'openclasp_verify_receipt',
54
  'openclasp_setup',
55
  'openclasp_get_identity',
56
  'openclasp_switch_agent',
57
  'openclasp_update_profile',
58
  'openclasp_connection_status',
59
  'openclasp_register_delegation',
60
  'openclasp_save_contract',
61
  'openclasp_permit_mediation',
62
  'openclasp_resolve_dispute',
63
  'openclasp_find_agent',
64
  'openclasp_search_agents',
65
  'openclasp_connect_to_agent',
66
  'openclasp_list_invitations',
67
  'openclasp_respond_invitation',
68
  'openclasp_get_shared_interaction',
69
  'openclasp_get_live_session',
70
  'openclasp_record_session_event',
71
  'openclasp_complete_live_session',
72
  'openclasp_heartbeat',
73
  'openclasp_submit_completion_report',
74
  'openclasp_list_feedback_requests',
75
  'openclasp_submit_interaction_feedback',
76
  'openclasp_checkpoint',
77
  'openclasp_resolve_agent',
78
  'openclasp_propose_contract_revision',
79
  'openclasp_respond_contract_revision',
80
  'openclasp_get_contextual_intelligence',
81
  'openclasp_recommend_agents',
82
  'openclasp_generate_assurance_probe',
83
  'openclasp_list_assurance_probes',
84
  'openclasp_submit_assurance_response',
85
  'openclasp_get_assurance_comparisons',
86
  'openclasp_get_assurance_brief',
87
  'openclasp_decide_assurance_safeguard',
88
  'openclasp_shield_open_case',
89
  'openclasp_shield_consult',
90
  'openclasp_shield_get_case',
91
  'openclasp_shield_list_cases',
92
  'openclasp_shield_close_case',
93
] as const;
94
95
export const HOSTED_OPENCLASP_TOOL_NAMES = OPENCLASP_TOOL_NAMES.filter(
96
  (name) => name !== 'openclasp_create_identity',
97
);
98
99
const openClaspDashboardUrl = () =>
100
  (
101
    process.env.OPENCLASP_PUBLIC_URL ??
102
    process.env.OPENCLASP_DASHBOARD_URL ??
103
    'https://openclasp.vercel.app'
104
  ).replace(/\/$/, '');
105
106
const READ_ONLY_TOOL = {
107
  readOnlyHint: true,
108
  destructiveHint: false,
109
  idempotentHint: true,
110
} as const;
111
const WRITE_TOOL = {
112
  readOnlyHint: false,
113
  destructiveHint: false,
114
  idempotentHint: false,
115
} as const;
116
117
const text = (value: unknown) => ({
118
  content: [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }],
119
});
120
121
const LiveSessionEventInputSchema = z
122
  .object({
123
    interactionId: LiveSessionEventSchema.shape.interactionId,
124
    sequence: LiveSessionEventSchema.shape.sequence,
125
    type: LiveSessionEventSchema.shape.type,
126
    occurredAt: LiveSessionEventSchema.shape.occurredAt,
127
    messageHash: LiveSessionEventSchema.shape.messageHash,
128
    evidenceReferences: LiveSessionEventSchema.shape.evidenceReferences,
129
    outcome: LiveSessionEventSchema.shape.outcome,
130
    checkpoint: LiveSessionEventSchema.shape.checkpoint,
131
    details: LiveSessionEventSchema.shape.details,
132
  })
133
  .strict()
134
  .superRefine((event, context) => {
135
    if (event.type === 'progress_checkpoint' && !event.checkpoint)
136
      context.addIssue({
137
        code: 'custom',
138
        path: ['checkpoint'],
139
        message: 'Progress checkpoint events require structured checkpoint data',
140
      });
141
    if (event.type !== 'progress_checkpoint' && event.checkpoint)
142
      context.addIssue({
143
        code: 'custom',
144
        path: ['checkpoint'],
145
        message: 'Checkpoint data is only valid on progress checkpoint events',
146
      });
147
  });
148
149
export const OPENCLASP_MCP_INSTRUCTIONS =
150
  'Start with openclasp_connection_status and heartbeat while active. Before a consequential decision, open or consult a Shield case and provide only bounded facts, evidence, and policy. For agent agreements, use pre-task and post-task assurance. Checkpoint when blocked or drifting. Record terminal outcomes honestly. Never upload transcripts, chain-of-thought, secrets, or invented evidence.';
151
152
export function buildMcpServer(engine = new TrustEngine()) {
153
  const server = new McpServer(
154
    { name: 'openclasp', version: '0.1.0' },
155
    {
156
      instructions: OPENCLASP_MCP_INSTRUCTIONS,
157
    },
158
  );
159
  return registerOpenClaspTools(server, engine);
160
}
161
162
type HostedRecord = (
163
  operatorId: string,
164
  kind:
165
    | 'agent'
166
    | 'delegation'
167
    | 'contract'
168
    | 'interaction'
169
    | 'event'
170
    | 'receipt'
171
    | 'feedback'
172
    | 'conflict'
173
    | 'profile',
174
  recordId: string,
175
  value: unknown,
176
) => Promise<void>;
177
178
type ToolContext = {
179
  http?: {
180
    authInfo?: { clientId?: string; extra?: Record<string, unknown> };
181
  };
182
};
183
type EngineSource = TrustEngine | ((context: ToolContext) => Promise<TrustEngine>);
184
type AgentDirectory = {
185
  publishAgent?(operatorId: string, card: PublicAgentCard): Promise<PublicAgentCard>;
186
  getPublishedAgent(agentId: string): Promise<PublicAgentCard | undefined>;
187
  resolveAgentReference?(reference: string): Promise<any | undefined>;
188
  searchPublishedAgents(input: {
189
    query?: string | undefined;
190
    capability?: string | undefined;
191
    limit?: number | undefined;
192
  }): Promise<PublicAgentCard[]>;
193
  listContextualIntelligence?(
194
    operatorId: string,
195
    input?: { agentId?: string; taskCategory?: string },
196
  ): Promise<any[]>;
197
  searchPersonalizedMarketplace?(
198
    operatorId: string,
199
    input?: { agentId?: string; taskCategory?: string; query?: string; limit?: number },
200
  ): Promise<any[]>;
201
  createFederatedInteraction(
202
    operatorId: string,
203
    value: FederatedInteraction,
204
  ): Promise<FederatedInteraction>;
205
  listFederatedInteractions(operatorId: string): Promise<FederatedInteraction[]>;
206
  getFederatedInteraction(
207
    operatorId: string,
208
    interactionId: string,
209
  ): Promise<FederatedInteraction | undefined>;
210
  respondToFederatedInteraction(
211
    operatorId: string,
212
    interactionId: string,
213
    agentId: string,
214
    decision: 'accept' | 'reject',
215
    method?: 'oauth_installation' | 'oauth_account' | 'policy_auto_accept',
216
  ): Promise<FederatedInteraction>;
217
  proposeContractRevision?(
218
    operatorId: string,
219
    interactionId: string,
220
    agentId: string,
221
    contract: z.infer<typeof InteractionContractSchema>,
222
    expectedTermsHash?: string,
223
    method?: 'oauth_installation' | 'oauth_account',
224
  ): Promise<FederatedInteraction>;
225
  respondToContractRevision?(
226
    operatorId: string,
227
    interactionId: string,
228
    agentId: string,
229
    revisionId: string,
230
    decision: 'accept' | 'reject',
231
    method?: 'oauth_installation' | 'oauth_account',
232
  ): Promise<FederatedInteraction>;
233
  getLiveSession(operatorId: string, interactionId: string, agentId: string): Promise<any>;
234
  recordLiveSessionEvent(
235
    token: string,
236
    event: z.infer<typeof LiveSessionEventSchema>,
237
  ): Promise<unknown>;
238
  submitCompletionReport?(
239
    operatorId: string,
240
    agentId: string,
241
    report: z.infer<typeof InteractionCompletionReportSchema>,
242
    submissionMethod:
243
      'oauth_account' | 'oauth_installation' | 'agent_access_token' | 'runtime_session',
244
  ): Promise<unknown>;
245
  listFeedbackRequests?(operatorId: string, agentId: string): Promise<unknown[]>;
246
  submitInteractionFeedback?(
247
    operatorId: string,
248
    agentId: string,
249
    feedback: z.infer<typeof InteractionFeedbackSchema>,
250
    submissionMethod:
251
      'oauth_account' | 'oauth_installation' | 'agent_access_token' | 'runtime_session',
252
  ): Promise<unknown>;
253
  getAssuranceProbeContext?(
254
    operatorId: string,
255
    interactionId: string,
256
    generatedForAgentId: string,
257
    targetAgentId: string,
258
  ): Promise<any>;
259
  beginAssuranceGeneration?(record: any): Promise<void>;
260
  finishAssuranceGeneration?(operatorId: string, generationId: string, value: any): Promise<void>;
261
  saveAssuranceProbePlan?(operatorId: string, plan: any): Promise<any>;
262
  saveAssuranceDecision?(operatorId: string, decision: any, plan: any): Promise<any>;
263
  listAssuranceProbePlans?(
264
    operatorId: string,
265
    interactionId: string,
266
    agentId: string,
267
  ): Promise<any[]>;
268
  submitAssuranceProbeResponse?(operatorId: string, agentId: string, value: any): Promise<any>;
269
  listAssuranceComparisons?(
270
    operatorId: string,
271
    interactionId: string,
272
    agentId: string,
273
  ): Promise<any[]>;
274
  getAssuranceBrief?(operatorId: string, interactionId: string, agentId: string): Promise<any>;
275
  decideAssuranceSafeguard?(
276
    operatorId: string,
277
    interactionId: string,
278
    agentId: string,
279
    safeguardId: string,
280
    status: 'accepted' | 'rejected' | 'modified',
281
    decisionReason?: string,
282
  ): Promise<any>;
283
  saveShieldCase?(operatorId: string, value: any): Promise<any>;
284
  getShieldCase?(operatorId: string, caseId: string): Promise<any | undefined>;
285
  listShieldCases?(operatorId: string, agentId?: string): Promise<any[]>;
286
  saveShieldConsultation?(operatorId: string, value: any): Promise<any>;
287
  listShieldConsultations?(operatorId: string, caseId: string): Promise<any[]>;
288
  saveShieldOutcome?(operatorId: string, value: any): Promise<any>;
289
  touchAgentPresence(operatorId: string, agentId: string): Promise<unknown>;
290
};
291
292
function installationContext(context: ToolContext) {
293
  const operatorId = context.http?.authInfo?.extra?.operatorId;
294
  const clientId = context.http?.authInfo?.clientId;
295
  if (typeof operatorId !== 'string' || typeof clientId !== 'string')
296
    throw new Error('Authenticated MCP installation context is required');
297
  const boundAgentId = context.http?.authInfo?.extra?.boundAgentId;
298
  const credentialType = context.http?.authInfo?.extra?.credentialType;
299
  return {
300
    operatorId,
301
    clientId,
302
    ...(typeof boundAgentId === 'string' ? { boundAgentId } : {}),
303
    ...(typeof credentialType === 'string' ? { credentialType } : {}),
304
  };
305
}
306
307
export function registerOpenClaspTools(
308
  server: McpServer,
309
  engineSource: EngineSource = new TrustEngine(),
310
  recordHosted?: HostedRecord,
311
  onboardingStore?: OnboardingStore,
312
  agentDirectory?: AgentDirectory,
313
) {
314
  const factChecker = new UnavailableFactCheckProvider();
315
  const resolveEngine = (context: ToolContext) =>
316
    typeof engineSource === 'function' ? engineSource(context) : Promise.resolve(engineSource);
317
  const persist = async (
318
    context: ToolContext,
319
    kind: Parameters<HostedRecord>[1],
320
    id: string,
321
    value: unknown,
322
  ) => {
323
    const operatorId = context.http?.authInfo?.extra?.operatorId;
324
    if (recordHosted && typeof operatorId === 'string')
325
      await recordHosted(operatorId, kind, id, value);
326
  };
327
328
  const resolveAuthenticatedInstallation = async (context: ToolContext) => {
329
    if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
330
    const connection = installationContext(context);
331
    const binding = await resolveInstallation(
332
      onboardingStore,
333
      connection.operatorId,
334
      connection.clientId,
335
    );
336
    if (
337
      connection.boundAgentId &&
338
      (binding.status !== 'connected' || binding.agent.agentId !== connection.boundAgentId)
339
    )
340
      throw new Error('Agent access token binding is invalid');
341
    return { connection, binding };
342
  };
343
344
  const requireBoundAgent = async (context: ToolContext, claimedAgentId?: string) => {
345
    if (!onboardingStore) return undefined;
346
    const { connection, binding } = await resolveAuthenticatedInstallation(context);
347
    if (binding.status !== 'connected')
348
      throw new Error('Call openclasp_setup and obtain owner confirmation before using this tool');
349
    if (claimedAgentId && binding.agent.agentId !== claimedAgentId)
350
      throw new Error('The claimed agent does not match this MCP installation');
351
    if (agentDirectory)
352
      await agentDirectory.touchAgentPresence(connection.operatorId, binding.agent.agentId);
353
    return binding;
354
  };
355
356
  if (!onboardingStore)
357
    server.registerTool(
358
      OPENCLASP_TOOL_NAMES[0],
359
      {
360
        title: 'Create local cryptographic identity',
361
        description:
362
          'Create an Ed25519 identity for local development. This returns private key material and is intentionally unavailable on hosted MCP.',
363
        inputSchema: z.object({
364
          agentId: z.string(),
365
          operatorRef: z.string(),
366
          capabilities: z.array(z.string()),
367
        }),
368
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
369
      },
370
      async (input, context) => {
371
        const authenticatedOperator = context.http?.authInfo?.extra?.operatorId;
372
        const created = createIdentity({
373
          ...input,
374
          operatorRef:
375
            typeof authenticatedOperator === 'string'
376
              ? `auth0:${authenticatedOperator}`
377
              : input.operatorRef,
378
        });
379
        return text(created);
380
      },
381
    );
382
  server.registerTool(
383
    OPENCLASP_TOOL_NAMES[1],
384
    {
385
      title: 'Register cryptographic identity',
386
      description: 'Register and verify a signed agent identity',
387
      inputSchema: AgentIdentitySchema,
388
      annotations: { ...WRITE_TOOL, idempotentHint: true },
389
    },
390
    async (input, context) => {
391
      const authenticatedOperator = context.http?.authInfo?.extra?.operatorId;
392
      if (
393
        typeof authenticatedOperator === 'string' &&
394
        input.operatorRef !== `auth0:${authenticatedOperator}`
395
      ) {
396
        throw new Error('Agent identity is not owned by the authenticated operator');
397
      }
398
      await requireBoundAgent(context, input.agentId);
399
      const engine = await resolveEngine(context);
400
      const registered = engine.registerAgent(input);
401
      await persist(context, 'agent', registered.agentId, registered);
402
      return text(registered);
403
    },
404
  );
405
  server.registerTool(
406
    OPENCLASP_TOOL_NAMES[2],
407
    {
408
      title: 'Get contextual reliability',
409
      description: 'Get a task-specific behavioural profile',
410
      inputSchema: z.object({ agentId: z.string(), version: z.string(), taskCategory: z.string() }),
411
      annotations: READ_ONLY_TOOL,
412
    },
413
    async (input, context) => {
414
      await requireBoundAgent(context);
415
      const engine = await resolveEngine(context);
416
      return text(engine.getRisk(input.agentId, input.version, input.taskCategory));
417
    },
418
  );
419
  server.registerTool(
420
    OPENCLASP_TOOL_NAMES[3],
421
    {
422
      title: 'Assess counterparty',
423
      description: 'Assess a counterparty in context',
424
      inputSchema: z.object({
425
        envelope: TrustEnvelopeSchema,
426
        action: z.string(),
427
        dataClasses: z.array(z.string()).optional(),
428
        humanApproved: z.boolean().optional(),
429
      }),
430
      annotations: READ_ONLY_TOOL,
431
    },
432
    async (input, context) => {
433
      await requireBoundAgent(context);
434
      const engine = await resolveEngine(context);
435
      return text(engine.assess(input as any));
436
    },
437
  );
438
  server.registerTool(
439
    OPENCLASP_TOOL_NAMES[4],
440
    {
441
      title: 'Begin assured interaction',
442
      description: 'Begin an interaction and return its identifier',
443
      inputSchema: z.object({
444
        purpose: z.string(),
445
        parties: z.array(z.string()),
446
        taskCategory: z.string(),
447
      }),
448
      annotations: WRITE_TOOL,
449
    },
450
    async (input, context) => {
451
      const binding = await requireBoundAgent(context);
452
      if (binding && !input.parties.includes(binding.agent.agentId))
453
        throw new Error('Interaction parties must include the agent bound to this installation');
454
      const interaction = {
455
        interactionId: crypto.randomUUID(),
456
        status: 'contract_required',
457
        createdAt: new Date().toISOString(),
458
        ...input,
459
      };
460
      await persist(context, 'interaction', interaction.interactionId, interaction);
461
      return text(interaction);
462
    },
463
  );
464
  server.registerTool(
465
    OPENCLASP_TOOL_NAMES[5],
466
    {
467
      title: 'Record signed event',
468
      description: 'Validate and record a signed structured event',
469
      inputSchema: InteractionEventSchema,
470
      annotations: { ...WRITE_TOOL, idempotentHint: true },
471
    },
472
    async (input, context) => {
473
      await requireBoundAgent(context, input.agentId);
474
      const engine = await resolveEngine(context);
475
      const event = engine.recordEvent(input);
476
      await persist(context, 'event', event.eventId, event);
477
      return text(event);
478
    },
479
  );
480
  server.registerTool(
481
    OPENCLASP_TOOL_NAMES[6],
482
    {
483
      title: 'Check factual claim',
484
      description: 'Check an objective factual claim',
485
      inputSchema: z.object({ claim: z.string(), permission: z.boolean().optional() }),
486
      annotations: { ...READ_ONLY_TOOL, openWorldHint: true },
487
    },
488
    async (input) => text(await factChecker.check(input.claim, input.permission)),
489
  );
490
  server.registerTool(
491
    OPENCLASP_TOOL_NAMES[7],
492
    {
493
      title: 'Validate commitment',
494
      description: 'Check whether a commitment is represented by a signed event',
495
      inputSchema: z.object({ interactionId: z.string().uuid(), commitment: z.string() }),
496
      annotations: READ_ONLY_TOOL,
497
    },
498
    async (input, context) => {
499
      await requireBoundAgent(context);
500
      const engine = await resolveEngine(context);
501
      return text({
502
        valid: [...engine.events.values()].some(
503
          (event) =>
504
            event.interactionId === input.interactionId &&
505
            event.eventType === 'commitment' &&
506
            JSON.stringify(event.payload).includes(input.commitment),
507
        ),
508
      });
509
    },
510
  );
511
  server.registerTool(
512
    OPENCLASP_TOOL_NAMES[8],
513
    {
514
      title: 'Suggest dispute resolution',
515
      description: 'Suggest attributable resolutions for a consented conflict',
516
      inputSchema: z.object({ conflictId: z.string() }),
517
      annotations: READ_ONLY_TOOL,
518
    },
519
    async (input, context) => {
520
      const binding = await requireBoundAgent(context);
521
      const engine = await resolveEngine(context);
522
      const conflict = engine.conflicts.get(input.conflictId);
523
      if (binding && (!conflict || !(binding.agent.agentId in conflict.permissions)))
524
        throw new Error('The bound agent is not a participant in this dispute');
525
      return text(engine.conflicts.get(input.conflictId)?.possibleResolutions ?? []);
526
    },
527
  );
528
  server.registerTool(
529
    OPENCLASP_TOOL_NAMES[9],
530
    {
531
      title: 'Complete interaction',
532
      description: 'Submit a signed completion receipt',
533
      inputSchema: ReceiptSchema,
534
      annotations: { ...WRITE_TOOL, idempotentHint: true },
535
    },
536
    async (input, context) => {
537
      const binding = await requireBoundAgent(context);
538
      if (binding && !input.participants.includes(binding.agent.agentId))
539
        throw new Error('Receipt participants must include the bound agent');
540
      const engine = await resolveEngine(context);
541
      const receipt = engine.submitReceipt(input);
542
      await persist(context, 'receipt', receipt.receiptId, receipt);
543
      return text(receipt);
544
    },
545
  );
546
  server.registerTool(
547
    OPENCLASP_TOOL_NAMES[10],
548
    {
549
      title: 'Submit bilateral feedback',
550
      description: 'Submit receipt-backed structured feedback',
551
      inputSchema: FeedbackSchema,
552
      annotations: { ...WRITE_TOOL, idempotentHint: true },
553
    },
554
    async (input, context) => {
555
      await requireBoundAgent(context, input.reviewerAgentId);
556
      const engine = await resolveEngine(context);
557
      const result = engine.submitFeedback(input);
558
      await persist(context, 'feedback', input.feedbackId, input);
559
      for (const profile of engine.profiles.values())
560
        await persist(
561
          context,
562
          'profile',
563
          `${profile.agentId}|${profile.agentVersion}|${profile.taskCategory}`,
564
          profile,
565
        );
566
      return text(result);
567
    },
568
  );
569
  server.registerTool(
570
    OPENCLASP_TOOL_NAMES[11],
571
    {
572
      title: 'Raise dispute',
573
      description: 'Create a mutually consented dispute',
574
      inputSchema: z.object({
575
        interactionId: z.string().uuid(),
576
        issue: z.string(),
577
        participants: z.array(z.string()).min(2),
578
      }),
579
      annotations: WRITE_TOOL,
580
    },
581
    async (input, context) => {
582
      const binding = await requireBoundAgent(context);
583
      if (binding && !input.participants.includes(binding.agent.agentId))
584
        throw new Error('Dispute participants must include the bound agent');
585
      const engine = await resolveEngine(context);
586
      const conflict = engine.createConflict({
587
        interactionId: input.interactionId,
588
        issue: input.issue,
589
        participants: input.participants,
590
        positions: {},
591
        evidence: [],
592
        contractClauses: [],
593
        missingInformation: [],
594
        possibleResolutions: [],
595
      });
596
      await persist(context, 'conflict', conflict.conflictId, conflict);
597
      return text(conflict);
598
    },
599
  );
600
  server.registerTool(
601
    OPENCLASP_TOOL_NAMES[12],
602
    {
603
      title: 'Verify receipt',
604
      description: 'Verify a signed interaction receipt',
605
      inputSchema: ReceiptSchema,
606
      annotations: READ_ONLY_TOOL,
607
    },
608
    async (input, context) => {
609
      await requireBoundAgent(context);
610
      const engine = await resolveEngine(context);
611
      try {
612
        engine.verifyReceipt(input);
613
        return text({ valid: true });
614
      } catch {
615
        return text({ valid: false });
616
      }
617
    },
618
  );
619
  server.registerTool(
620
    OPENCLASP_TOOL_NAMES[13],
621
    {
622
      title: 'Set up this agent',
623
      description:
624
        'Propose this installation and safe automation policy. Conversation traffic stays on the agent-owned runtime.',
625
      inputSchema: z
626
        .object({
627
          agentName: z.string().trim().min(1).max(100),
628
          projectName: z.string().trim().min(1).max(100).optional(),
629
          projectId: z.string().optional(),
630
          framework: z.string().trim().max(100).optional(),
631
          description: z.string().trim().max(500).optional(),
632
          agentVersion: z.string().trim().min(1).max(100).optional(),
633
          autoAcceptPolicy: z.enum(['off', 'safe_matching']).optional(),
634
          autoAcceptTaskCategories: z.array(z.string().trim().min(1).max(100)).max(100).optional(),
635
          capabilities: z.array(z.string().trim().min(1).max(100)).max(100).optional(),
636
          limitations: z.array(z.string().trim().min(1).max(300)).max(100).optional(),
637
        })
638
        .refine((input) => input.projectName || input.projectId, {
639
          message: 'projectName or projectId is required',
640
        }),
641
      annotations: WRITE_TOOL,
642
    },
643
    async (input, context) => {
644
      if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
645
      const { connection, binding: current } = await resolveAuthenticatedInstallation(context);
646
      if (current.status === 'connected') {
647
        await agentDirectory?.touchAgentPresence(connection.operatorId, current.agent.agentId);
648
        return text(current);
649
      }
650
      const request = await requestAgentSetup(onboardingStore, connection.operatorId, {
651
        clientId: connection.clientId,
652
        ...input,
653
      });
654
      return text({
655
        status: 'pending_confirmation',
656
        confirmationRequired: true,
657
        request,
658
        confirmationUrl: `${openClaspDashboardUrl()}/connect`,
659
        next: 'Ask the owner to confirm this setup request in the OpenClasp dashboard, then call openclasp_get_identity.',
660
      });
661
    },
662
  );
663
  server.registerTool(
664
    OPENCLASP_TOOL_NAMES[14],
665
    {
666
      title: 'Get my agent identity',
667
      description:
668
        'Return the agent identity automatically bound to this authenticated MCP installation.',
669
      inputSchema: z.object({}),
670
      annotations: READ_ONLY_TOOL,
671
    },
672
    async (_input, context) => {
673
      if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
674
      const { connection, binding } = await resolveAuthenticatedInstallation(context);
675
      if (binding.status === 'connected')
676
        await agentDirectory?.touchAgentPresence(connection.operatorId, binding.agent.agentId);
677
      return text(binding);
678
    },
679
  );
680
  server.registerTool(
681
    OPENCLASP_TOOL_NAMES[15],
682
    {
683
      title: 'Switch agent identity',
684
      description:
685
        'Request that this MCP installation switch to another existing agent. Dashboard confirmation is required.',
686
      inputSchema: z.object({ existingAgentId: z.string().min(1) }),
687
      annotations: WRITE_TOOL,
688
    },
689
    async (input, context) => {
690
      if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
691
      const connection = installationContext(context);
692
      if (connection.credentialType === 'agent_access_token')
693
        throw new Error('Agent access tokens cannot switch agent identity');
694
      const request = await requestAgentSetup(onboardingStore, connection.operatorId, {
695
        clientId: connection.clientId,
696
        action: 'switch',
697
        existingAgentId: input.existingAgentId,
698
      });
699
      return text({
700
        status: 'pending_confirmation',
701
        confirmationRequired: true,
702
        request,
703
        confirmationUrl: `${openClaspDashboardUrl()}/connect`,
704
      });
705
    },
706
  );
707
  server.registerTool(
708
    OPENCLASP_TOOL_NAMES[16],
709
    {
710
      title: 'Update my agent profile',
711
      description: 'Update the profile of the agent bound to this MCP installation.',
712
      inputSchema: z.object({
713
        name: z.string().trim().min(1).max(100).optional(),
714
        framework: z.string().trim().min(1).max(100).optional(),
715
        description: z.string().trim().max(500).optional(),
716
        agentVersion: z.string().trim().min(1).max(100).optional(),
717
        autoPublish: z.boolean().optional(),
718
        autoAcceptPolicy: z.enum(['off', 'safe_matching']).optional(),
719
        autoAcceptTaskCategories: z.array(z.string().trim().min(1).max(100)).max(100).optional(),
720
        capabilities: z.array(z.string().trim().min(1).max(100)).max(100).optional(),
721
        limitations: z.array(z.string().trim().min(1).max(300)).max(100).optional(),
722
      }),
723
      annotations: WRITE_TOOL,
724
    },
725
    async (input, context) => {
726
      if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
727
      const { connection } = await resolveAuthenticatedInstallation(context);
728
      const agent = await updateAgentProfile(
729
        onboardingStore,
730
        connection.operatorId,
731
        connection.clientId,
732
        input,
733
      );
734
      const published = await agentDirectory?.getPublishedAgent(agent.agentId);
735
      if (published && agentDirectory?.publishAgent)
736
        await agentDirectory.publishAgent(
737
          connection.operatorId,
738
          buildPublicAgentCard(agent, openClaspDashboardUrl(), published),
739
        );
740
      return text(agent);
741
    },
742
  );
743
  server.registerTool(
744
    OPENCLASP_TOOL_NAMES[17],
745
    {
746
      title: 'Check OpenClasp connection',
747
      description:
748
        'Check whether this MCP installation is unbound, awaiting confirmation, or connected.',
749
      inputSchema: z.object({}),
750
      annotations: READ_ONLY_TOOL,
751
    },
752
    async (_input, context) => {
753
      if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
754
      const { connection, binding } = await resolveAuthenticatedInstallation(context);
755
      if (binding.status === 'connected') {
756
        await agentDirectory?.touchAgentPresence(connection.operatorId, binding.agent.agentId);
757
        return text(binding);
758
      }
759
      const state = await onboardingStore.list(connection.operatorId);
760
      const pending = state
761
        .filter((row) => row.kind === 'setup_request')
762
        .map((row) => row.payload as { clientId?: string; status?: string })
763
        .find(
764
          (request) => request.clientId === connection.clientId && request.status === 'pending',
765
        );
766
      return text(
767
        pending
768
          ? {
769
              status: 'pending_confirmation',
770
              confirmationUrl: `${openClaspDashboardUrl()}/connect`,
771
            }
772
          : binding,
773
      );
774
    },
775
  );
776
  server.registerTool(
777
    OPENCLASP_TOOL_NAMES[18],
778
    {
779
      title: 'Register delegation',
780
      description: 'Register and verify a signed, scoped delegation between two known agents.',
781
      inputSchema: DelegationCredentialSchema,
782
      annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
783
    },
784
    async (input, context) => {
785
      const binding = await requireBoundAgent(context);
786
      if (
787
        binding &&
788
        input.parentAgentId !== binding.agent.agentId &&
789
        input.childAgentId !== binding.agent.agentId
790
      )
791
        throw new Error('The bound agent is not a party to this delegation');
792
      const engine = await resolveEngine(context);
793
      engine.delegations.set(input.delegationId, input);
794
      if (!engine.verifyDelegation(input.delegationId)) {
795
        engine.delegations.delete(input.delegationId);
796
        throw new Error('Delegation verification failed');
797
      }
798
      await persist(context, 'delegation', input.delegationId, input);
799
      return text({ valid: true, delegation: input });
800
    },
801
  );
802
  server.registerTool(
803
    OPENCLASP_TOOL_NAMES[19],
804
    {
805
      title: 'Save signed agreement',
806
      description:
807
        'Verify and save a complete interaction contract signed by every participating agent.',
808
      inputSchema: InteractionContractSchema,
809
      annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
810
    },
811
    async (input, context) => {
812
      const binding = await requireBoundAgent(context);
813
      if (binding && !input.parties.includes(binding.agent.agentId))
814
        throw new Error('Contract parties must include the bound agent');
815
      const contract = (await resolveEngine(context)).saveContract(input);
816
      await persist(context, 'contract', contract.interactionId, contract);
817
      return text(contract);
818
    },
819
  );
820
  server.registerTool(
821
    OPENCLASP_TOOL_NAMES[20],
822
    {
823
      title: 'Consent to mediation',
824
      description:
825
        "Record the bound agent's explicit consent to mediation for a dispute it participates in.",
826
      inputSchema: z.object({ conflictId: z.string(), agentId: z.string().optional() }),
827
      annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
828
    },
829
    async (input, context) => {
830
      const binding = await requireBoundAgent(context);
831
      const agentId = binding?.agent.agentId ?? input.agentId;
832
      if (!agentId) throw new Error('agentId is required for local MCP');
833
      const engine = await resolveEngine(context);
834
      const conflict = engine.permitMediation(input.conflictId, agentId);
835
      await persist(context, 'conflict', conflict.conflictId, conflict);
836
      return text(conflict);
837
    },
838
  );
839
  server.registerTool(
840
    OPENCLASP_TOOL_NAMES[21],
841
    {
842
      title: 'Resolve mediated dispute',
843
      description:
844
        'Save an agreed resolution only after every dispute participant has consented to mediation.',
845
      inputSchema: z.object({ conflictId: z.string(), resolution: z.string().trim().min(1) }),
846
      annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
847
    },
848
    async (input, context) => {
849
      const binding = await requireBoundAgent(context);
850
      const engine = await resolveEngine(context);
851
      const existing = engine.conflicts.get(input.conflictId);
852
      if (binding && (!existing || !(binding.agent.agentId in existing.permissions)))
853
        throw new Error('The bound agent is not a participant in this dispute');
854
      const conflict = engine.resolveConflict(input.conflictId, input.resolution);
855
      await persist(context, 'conflict', conflict.conflictId, conflict);
856
      return text(conflict);
857
    },
858
  );
859
  server.registerTool(
860
    OPENCLASP_TOOL_NAMES[22],
861
    {
862
      title: 'Find published agent',
863
      description:
864
        'Find an exact owner-published agent card. Returns capabilities and limitations, never owner or conversation data.',
865
      inputSchema: z.object({ agentId: z.string().min(1) }),
866
      annotations: READ_ONLY_TOOL,
867
    },
868
    async (input, context) => {
869
      await requireBoundAgent(context);
870
      if (!agentDirectory) throw new Error('The shared agent directory is not configured');
871
      return text((await agentDirectory.getPublishedAgent(input.agentId)) ?? { found: false });
872
    },
873
  );
874
  server.registerTool(
875
    OPENCLASP_TOOL_NAMES[23],
876
    {
877
      title: 'Search published agents',
878
      description:
879
        'Search owner-published agent cards by name, framework, or capability without exposing owner identities or projects.',
880
      inputSchema: z.object({
881
        query: z.string().trim().max(100).optional(),
882
        capability: z.string().trim().max(100).optional(),
883
        limit: z.number().int().min(1).max(50).optional(),
884
      }),
885
      annotations: READ_ONLY_TOOL,
886
    },
887
    async (input, context) => {
888
      await requireBoundAgent(context);
889
      if (!agentDirectory) throw new Error('The shared agent directory is not configured');
890
      return text(await agentDirectory.searchPublishedAgents(input));
891
    },
892
  );
893
  server.registerTool(
894
    OPENCLASP_TOOL_NAMES[24],
895
    {
896
      title: 'Automatically connect to another agent',
897
      description:
898
        'Infer safe contract defaults, auto-activate when the responder policy permits, and return a ready-to-send A2A request.',
899
      inputSchema: z
900
        .object({
901
          targetAgentId: z.string().min(1).optional(),
902
          targetAgentReference: z.string().trim().min(1).max(2048).optional(),
903
          targetAgentCardUrl: z.string().url().optional(),
904
          task: z.string().trim().min(1).max(2000).optional(),
905
          purpose: z.string().trim().min(1).max(500).optional(),
906
          taskCategory: z.string().trim().min(1).max(100).optional(),
907
          requestedOutcome: z.string().trim().min(1).max(1000).optional(),
908
          successCriteria: z.array(z.string().trim().min(1)).min(1).max(50).optional(),
909
          allowedActions: z.array(z.string().trim().min(1)).max(100).default([]),
910
          prohibitedActions: z.array(z.string().trim().min(1)).max(100).default([]),
911
          allowedData: z.array(z.string().trim().min(1)).max(100).default([]),
912
          prohibitedData: z.array(z.string().trim().min(1)).max(100).default([]),
913
          evidenceRequirements: z.array(z.string().trim().min(1)).max(100).default([]),
914
          deadline: z.string().datetime().optional(),
915
          expiresInMinutes: z.number().int().min(5).max(10080).default(60),
916
        })
917
        .superRefine((value, context) => {
918
          if (!value.targetAgentId && !value.targetAgentReference && !value.targetAgentCardUrl)
919
            context.addIssue({
920
              code: 'custom',
921
              message: 'targetAgentId, targetAgentReference, or targetAgentCardUrl is required',
922
            });
923
          if (!value.task && !value.purpose)
924
            context.addIssue({ code: 'custom', message: 'task or purpose is required' });
925
        }),
926
      annotations: { ...WRITE_TOOL, openWorldHint: true },
927
    },
928
    async (input, context) => {
929
      if (!agentDirectory) throw new Error('Federated agent connections are not configured');
930
      const binding = await requireBoundAgent(context);
931
      if (!binding) throw new Error('A hosted, bound MCP installation is required');
932
      const connection = installationContext(context);
933
      let initiatorCard = await agentDirectory.getPublishedAgent(binding.agent.agentId);
934
      if (!initiatorCard && binding.agent.autoPublish && agentDirectory.publishAgent)
935
        initiatorCard = await agentDirectory.publishAgent(
936
          connection.operatorId,
937
          buildPublicAgentCard(binding.agent, openClaspDashboardUrl()),
938
        );
939
      if (!initiatorCard)
940
        throw new Error(
941
          'This agent is private. Enable automatic publishing once in its dashboard settings.',
942
        );
943
      let targetAgentId = input.targetAgentId;
944
      let resolvedResponderCard: PublicAgentCard | undefined;
945
      if (!targetAgentId && input.targetAgentReference) {
946
        if (!agentDirectory.resolveAgentReference)
947
          throw new Error('Agent reference resolution is not configured');
948
        const resolution = await agentDirectory.resolveAgentReference(input.targetAgentReference);
949
        resolvedResponderCard = resolution?.card as PublicAgentCard | undefined;
950
        targetAgentId = resolvedResponderCard?.agentId;
951
      }
952
      if (!targetAgentId && input.targetAgentCardUrl) {
953
        const cardUrl = new URL(input.targetAgentCardUrl);
954
        const trustedOrigin = new URL(openClaspDashboardUrl()).origin;
955
        const match = cardUrl.pathname.match(/^\/agents\/([^/]+)\/card\.json$/);
956
        if (cardUrl.origin !== trustedOrigin || !match?.[1])
957
          throw new Error('Only OpenClasp-hosted Agent Card URLs are accepted');
958
        targetAgentId = decodeURIComponent(match[1]);
959
      }
960
      if (!targetAgentId) throw new Error('Target agent is required');
961
      const responderCard =
962
        resolvedResponderCard ?? (await agentDirectory.getPublishedAgent(targetAgentId));
963
      if (!responderCard) throw new Error('Target agent is not published on OpenClasp');
964
      const responderTransport = responderCard.transports[0];
965
      if (!responderTransport) throw new Error('Target agent has not published an A2A endpoint');
966
      const task = input.task ?? input.purpose!;
967
      const purpose = input.purpose ?? task.slice(0, 500);
968
      const taskCategory = input.taskCategory ?? responderCard.capabilities[0] ?? 'general';
969
      const requestedOutcome = input.requestedOutcome ?? task.slice(0, 1000);
970
      const successCriteria = input.successCriteria ?? [
971
        'Return a clear result that directly addresses the requested task',
972
        'Disclose material limitations, uncertainty, and unresolved blockers',
973
      ];
974
      const evidenceRequirements = input.evidenceRequirements.length
975
        ? input.evidenceRequirements
976
        : ['Support material factual claims with inspectable evidence references'];
977
      const prohibitedData = input.prohibitedData.length
978
        ? input.prohibitedData
979
        : ['credentials', 'government identifiers', 'payment card data'];
980
      const existing = (await agentDirectory.listFederatedInteractions(connection.operatorId)).find(
981
        (candidate) =>
982
          candidate.initiatorAgentId === binding.agent.agentId &&
983
          candidate.responderAgentId === responderCard.agentId &&
984
          ['pending', 'active'].includes(candidate.status) &&
985
          candidate.contract.purpose === purpose &&
986
          candidate.contract.taskCategory === taskCategory &&
987
          candidate.contract.requestedOutcome === requestedOutcome &&
988
          JSON.stringify(candidate.contract.successCriteria) === JSON.stringify(successCriteria) &&
989
          JSON.stringify(candidate.contract.allowedActions) ===
990
            JSON.stringify(input.allowedActions) &&
991
          JSON.stringify(candidate.contract.prohibitedActions) ===
992
            JSON.stringify(input.prohibitedActions) &&
993
          JSON.stringify(candidate.contract.allowedData) === JSON.stringify(input.allowedData) &&
994
          JSON.stringify(candidate.contract.prohibitedData) === JSON.stringify(prohibitedData) &&
995
          JSON.stringify(candidate.contract.evidenceRequirements) ===
996
            JSON.stringify(evidenceRequirements) &&
997
          candidate.contract.deadline === input.deadline,
998
      );
999
      const now = new Date();
1000
      const interactionId = existing?.interactionId ?? crypto.randomUUID();
1001
      const contract = {
1002
        protocolVersion: '0.1' as const,
1003
        interactionId,
1004
        purpose,
1005
        parties: [binding.agent.agentId, responderCard.agentId],
1006
        taskCategory,
1007
        requestedOutcome,
1008
        successCriteria,
1009
        allowedActions: input.allowedActions,
1010
        prohibitedActions: input.prohibitedActions,
1011
        allowedData: input.allowedData,
1012
        prohibitedData,
1013
        ...(input.deadline ? { deadline: input.deadline } : {}),
1014
        evidenceRequirements,
1015
        delegationRules: ['explicit_contract_scope'],
1016
        humanApprovalRequirements: [],
1017
        factCheckingPolicy: 'important_claims',
1018
        mediationPolicy: 'mutual_consent' as const,
1019
        retentionDays: 30,
1020
        completionConditions: successCriteria,
1021
        cancellationConditions: ['either_party_before_completion'],
1022
        signatures: {},
1023
      };
1024
      const termsHash = existing?.termsHash ?? canonicalHash(contract);
1025
      const createdAt = now.toISOString();
1026
      const interaction: FederatedInteraction = {
1027
        protocolVersion: '0.1',
1028
        interactionId,
1029
        initiatorAgentId: binding.agent.agentId,
1030
        responderAgentId: responderCard.agentId,
1031
        status: 'pending',
1032
        contract,
1033
        termsHash,
1034
        acceptances: {
1035
          [binding.agent.agentId]: {
1036
            agentId: binding.agent.agentId,
1037
            method: 'oauth_installation',
1038
            termsHash,
1039
            acceptedAt: createdAt,
1040
          },
1041
        },
1042
        contractRevision: 1,
1043
        contractRevisions: [],
1044
        ...(initiatorCard.transports[0] ? { initiatorTransport: initiatorCard.transports[0] } : {}),
1045
        responderTransport,
1046
        createdAt,
1047
        updatedAt: createdAt,
1048
        expiresAt: new Date(now.getTime() + input.expiresInMinutes * 60_000).toISOString(),
1049
      };
1050
      const stored =
1051
        existing ??
1052
        (await agentDirectory.createFederatedInteraction(connection.operatorId, interaction));
1053
      const session =
1054
        stored.status === 'active'
1055
          ? await agentDirectory.getLiveSession(
1056
              connection.operatorId,
1057
              stored.interactionId,
1058
              binding.agent.agentId,
1059
            )
1060
          : undefined;
1061
      return text({
1062
        ready: stored.status === 'active',
1063
        session,
1064
        interaction: stored,
1065
        a2a: {
1066
          endpoint: session?.peer.endpoint,
1067
          bearerToken: session?.peer.bearerToken,
1068
          protocolBinding: stored.responderTransport.protocolBinding,
1069
          extensions: [DEFAULT_EXTENSION_URI],
1070
          metadata: {
1071
            [DEFAULT_EXTENSION_URI]: {
1072
              interactionId: stored.interactionId,
1073
              termsHash: stored.termsHash,
1074
              initiatorAgentId: stored.initiatorAgentId,
1075
              responderAgentId: stored.responderAgentId,
1076
            },
1077
          },
1078
          requestTemplate: {
1079
            method: 'message/send',
1080
            headers: { 'A2A-Extensions': DEFAULT_EXTENSION_URI },
1081
            message: {
1082
              role: 'user',
1083
              parts: [{ kind: 'text', text: task }],
1084
              metadata: {
1085
                [DEFAULT_EXTENSION_URI]: {
1086
                  interactionId: stored.interactionId,
1087
                  termsHash: stored.termsHash,
1088
                  initiatorAgentId: stored.initiatorAgentId,
1089
                  responderAgentId: stored.responderAgentId,
1090
                },
1091
              },
1092
            },
1093
          },
1094
        },
1095
        next:
1096
          stored.status === 'active'
1097
            ? 'Both live runtimes accepted. Send the A2A request directly to session.peer.endpoint; OpenClasp is not in the message path.'
1098
            : `The task needs responder approval. OpenClasp will expose it at ${openClaspDashboardUrl()}/dashboard; retry this interaction after approval.`,
1099
      });
1100
    },
1101
  );
1102
  server.registerTool(
1103
    OPENCLASP_TOOL_NAMES[25],
1104
    {
1105
      title: 'List shared invitations',
1106
      description: 'List incoming and outgoing cross-account interaction invitations and status.',
1107
      inputSchema: z.object({}),
1108
      annotations: READ_ONLY_TOOL,
1109
    },
1110
    async (_input, context) => {
1111
      if (!agentDirectory) throw new Error('Federated agent connections are not configured');
1112
      await requireBoundAgent(context);
1113
      const connection = installationContext(context);
1114
      return text(await agentDirectory.listFederatedInteractions(connection.operatorId));
1115
    },
1116
  );
1117
  server.registerTool(
1118
    OPENCLASP_TOOL_NAMES[26],
1119
    {
1120
      title: 'Respond to agent invitation',
1121
      description: 'Accept or reject an invitation as the agent bound to this MCP installation.',
1122
      inputSchema: z.object({
1123
        interactionId: z.string().uuid(),
1124
        decision: z.enum(['accept', 'reject']),
1125
      }),
1126
      annotations: WRITE_TOOL,
1127
    },
1128
    async (input, context) => {
1129
      if (!agentDirectory) throw new Error('Federated agent connections are not configured');
1130
      const binding = await requireBoundAgent(context);
1131
      if (!binding) throw new Error('A hosted, bound MCP installation is required');
1132
      const connection = installationContext(context);
1133
      return text(
1134
        await agentDirectory.respondToFederatedInteraction(
1135
          connection.operatorId,
1136
          input.interactionId,
1137
          binding.agent.agentId,
1138
          input.decision,
1139
          'oauth_installation',
1140
        ),
1141
      );
1142
    },
1143
  );
1144
  server.registerTool(
1145
    OPENCLASP_TOOL_NAMES[27],
1146
    {
1147
      title: 'Get shared interaction',
1148
      description: 'Get the canonical contract and bilateral acceptance state for one interaction.',
1149
      inputSchema: z.object({ interactionId: z.string().uuid() }),
1150
      annotations: READ_ONLY_TOOL,
1151
    },
1152
    async (input, context) => {
1153
      if (!agentDirectory) throw new Error('Federated agent connections are not configured');
1154
      await requireBoundAgent(context);
1155
      const connection = installationContext(context);
1156
      const value = await agentDirectory.getFederatedInteraction(
1157
        connection.operatorId,
1158
        input.interactionId,
1159
      );
1160
      if (!value) throw new Error('Interaction not found');
1161
      return text(value);
1162
    },
1163
  );
1164
  server.registerTool(
1165
    OPENCLASP_TOOL_NAMES[28],
1166
    {
1167
      title: 'Get direct live session',
1168
      description:
1169
        'Get the peer A2A endpoint and short-lived credential for a brokered live session.',
1170
      inputSchema: z.object({ interactionId: z.string().uuid() }),
1171
      annotations: READ_ONLY_TOOL,
1172
    },
1173
    async (input, context) => {
1174
      if (!agentDirectory) throw new Error('Live agent sessions are not configured');
1175
      const binding = await requireBoundAgent(context);
1176
      if (!binding) throw new Error('A bound MCP installation is required');
1177
      const connection = installationContext(context);
1178
      return text(
1179
        await agentDirectory.getLiveSession(
1180
          connection.operatorId,
1181
          input.interactionId,
1182
          binding.agent.agentId,
1183
        ),
1184
      );
1185
    },
1186
  );
1187
  server.registerTool(
1188
    OPENCLASP_TOOL_NAMES[29],
1189
    {
1190
      title: 'Record structured session event',
1191
      description:
1192
        'Record signed session metadata, hashes, evidence, corrections, or results without uploading raw messages.',
1193
      inputSchema: LiveSessionEventInputSchema,
1194
      annotations: WRITE_TOOL,
1195
    },
1196
    async (input, context) => {
1197
      if (!agentDirectory) throw new Error('Live agent sessions are not configured');
1198
      const binding = await requireBoundAgent(context);
1199
      if (!binding) throw new Error('A bound MCP installation is required');
1200
      const connection = installationContext(context);
1201
      const session = await agentDirectory.getLiveSession(
1202
        connection.operatorId,
1203
        input.interactionId,
1204
        binding.agent.agentId,
1205
      );
1206
      return text(
1207
        await agentDirectory.recordLiveSessionEvent(session.reporting.bearerToken, {
1208
          ...input,
1209
          eventId: crypto.randomUUID(),
1210
          agentId: binding.agent.agentId,
1211
        }),
1212
      );
1213
    },
1214
  );
1215
  server.registerTool(
1216
    OPENCLASP_TOOL_NAMES[30],
1217
    {
1218
      title: 'Complete live session',
1219
      description:
1220
        '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.',
1221
      inputSchema: z
1222
        .object({
1223
          interactionId: z.string().uuid(),
1224
          sequence: z.number().int().nonnegative(),
1225
          outcome: z.enum(['success', 'failure', 'partial']),
1226
          evidenceReferences: z.array(z.string().min(1).max(2048)).max(100).default([]),
1227
          details: LiveSessionEventSchema.shape.details,
1228
          assessment: z
1229
            .object({
1230
              summary: z.string().min(1).max(2000),
1231
              criteria: z
1232
                .array(
1233
                  z
1234
                    .object({
1235
                      criterion: z.string().min(1).max(1000),
1236
                      status: z.enum(['met', 'partially_met', 'missed', 'unknown']),
1237
                      explanation: z.string().max(1000).optional(),
1238
                      evidenceReferences: z.array(z.string().min(1).max(2048)).max(50).default([]),
1239
                    })
1240
                    .strict(),
1241
                )
1242
                .max(100)
1243
                .default([]),
1244
              deliverables: z.array(z.string().min(1).max(1000)).max(100).default([]),
1245
              actionsTaken: z.array(z.string().min(1).max(1000)).max(100).default([]),
1246
              blockers: z.array(z.string().min(1).max(1000)).max(100).default([]),
1247
              corrections: z.array(z.string().min(1).max(1000)).max(100).default([]),
1248
              confidence: z.number().min(0).max(1),
1249
            })
1250
            .strict()
1251
            .optional(),
1252
          feedback: z
1253
            .object({
1254
              ratings: z
1255
                .object({
1256
                  overall_satisfaction: z.number().min(0).max(1),
1257
                  outcome_satisfaction: z.number().min(0).max(1),
1258
                  communication: z.number().min(0).max(1),
1259
                  timeliness: z.number().min(0).max(1),
1260
                  scope_adherence: z.number().min(0).max(1),
1261
                  evidence_quality: z.number().min(0).max(1),
1262
                  correction_handling: z.number().min(0).max(1),
1263
                  reliability: z.number().min(0).max(1),
1264
                })
1265
                .strict(),
1266
              wouldWorkAgain: z.enum(['yes', 'no', 'unsure']),
1267
              reasonCodes: z.array(z.string().min(1).max(128)).max(32).default([]),
1268
              privateComment: z.string().max(1000).optional(),
1269
              confidence: z.number().min(0).max(1),
1270
            })
1271
            .strict()
1272
            .optional(),
1273
        })
1274
        .strict(),
1275
      annotations: WRITE_TOOL,
1276
    },
1277
    async (input, context) => {
1278
      if (!agentDirectory?.submitCompletionReport)
1279
        throw new Error('Live agent session completion is not configured');
1280
      const binding = await requireBoundAgent(context);
1281
      if (!binding) throw new Error('A bound MCP installation is required');
1282
      const connection = installationContext(context);
1283
      const session = await agentDirectory.getLiveSession(
1284
        connection.operatorId,
1285
        input.interactionId,
1286
        binding.agent.agentId,
1287
      );
1288
      const interaction = await agentDirectory.getFederatedInteraction(
1289
        connection.operatorId,
1290
        input.interactionId,
1291
      );
1292
      if (!interaction || !interaction.contract.parties.includes(binding.agent.agentId))
1293
        throw new Error('Bound agent is not an interaction participant');
1294
      const counterpartyAgentId =
1295
        interaction.initiatorAgentId === binding.agent.agentId
1296
          ? interaction.responderAgentId
1297
          : interaction.initiatorAgentId;
1298
      const occurredAt = new Date().toISOString();
1299
      const event = await agentDirectory.recordLiveSessionEvent(session.reporting.bearerToken, {
1300
        eventId: crypto.randomUUID(),
1301
        interactionId: input.interactionId,
1302
        agentId: binding.agent.agentId,
1303
        sequence: input.sequence,
1304
        type: input.outcome === 'failure' ? 'session_failed' : 'session_completed',
1305
        occurredAt,
1306
        evidenceReferences: input.evidenceReferences,
1307
        outcome: input.outcome,
1308
        details: input.details,
1309
      });
1310
      const suppliedCriteria = input.assessment?.criteria ?? [];
1311
      const report = InteractionCompletionReportSchema.parse({
1312
        reportId: crypto.randomUUID(),
1313
        interactionId: input.interactionId,
1314
        contractHash: interaction.termsHash,
1315
        reportingAgentId: binding.agent.agentId,
1316
        counterpartyAgentId,
1317
        agentVersion: binding.agent.agentVersion,
1318
        outcome: input.outcome,
1319
        summary:
1320
          input.assessment?.summary ??
1321
          `The agent reported this live session as ${input.outcome}. Detailed assessment was not supplied by the MCP client.`,
1322
        requestedOutcome: interaction.contract.requestedOutcome,
1323
        criteria: interaction.contract.successCriteria.map(
1324
          (criterion) =>
1325
            suppliedCriteria.find((candidate) => candidate.criterion === criterion) ?? {
1326
              criterion,
1327
              status: 'unknown',
1328
              evidenceReferences: [],
1329
            },
1330
        ),
1331
        deliverables: input.assessment?.deliverables ?? [],
1332
        actionsTaken: input.assessment?.actionsTaken ?? [],
1333
        blockers: input.assessment?.blockers ?? [],
1334
        scopeChanges: [],
1335
        corrections: input.assessment?.corrections ?? [],
1336
        evidenceReferences: input.evidenceReferences,
1337
        ...(session.activatedAt ? { startedAt: session.activatedAt } : {}),
1338
        completedAt: occurredAt,
1339
        confidence: input.assessment?.confidence ?? 0.25,
1340
        dataSharingMode: 'structured_only',
1341
      });
1342
      const submissionMethod =
1343
        connection.credentialType === 'agent_access_token'
1344
          ? 'agent_access_token'
1345
          : 'oauth_installation';
1346
      const completion = (await agentDirectory.submitCompletionReport(
1347
        connection.operatorId,
1348
        binding.agent.agentId,
1349
        report,
1350
        submissionMethod,
1351
      )) as { feedbackRequest?: { requestId?: string; status?: string } };
1352
      let feedback: unknown;
1353
      if (
1354
        input.feedback &&
1355
        completion.feedbackRequest?.requestId &&
1356
        completion.feedbackRequest.status !== 'submitted' &&
1357
        agentDirectory.submitInteractionFeedback
1358
      ) {
1359
        feedback = await agentDirectory.submitInteractionFeedback(
1360
          connection.operatorId,
1361
          binding.agent.agentId,
1362
          InteractionFeedbackSchema.parse({
1363
            feedbackId: crypto.randomUUID(),
1364
            requestId: completion.feedbackRequest.requestId,
1365
            interactionId: input.interactionId,
1366
            reviewerAgentId: binding.agent.agentId,
1367
            subjectAgentId: counterpartyAgentId,
1368
            reviewerAgentVersion: binding.agent.agentVersion,
1369
            ratings: input.feedback.ratings,
1370
            wouldWorkAgain: input.feedback.wouldWorkAgain,
1371
            reasonCodes: input.feedback.reasonCodes,
1372
            ...(input.feedback.privateComment
1373
              ? { privateComment: input.feedback.privateComment }
1374
              : {}),
1375
            evidenceReferences: input.evidenceReferences,
1376
            confidence: input.feedback.confidence,
1377
            submittedAt: occurredAt,
1378
          }),
1379
          submissionMethod,
1380
        );
1381
      }
1382
      return text({ event, completion, ...(feedback ? { feedback } : {}) });
1383
    },
1384
  );
1385
  server.registerTool(
1386
    OPENCLASP_TOOL_NAMES[31],
1387
    {
1388
      title: 'Heartbeat agent presence',
1389
      description:
1390
        'Mark this authenticated agent online. Online means activity within the last two minutes.',
1391
      inputSchema: z.object({}),
1392
      annotations: { ...WRITE_TOOL, idempotentHint: true },
1393
    },
1394
    async (_input, context) => {
1395
      if (!agentDirectory) throw new Error('Hosted agent presence is not configured');
1396
      if (!onboardingStore) throw new Error('Hosted agent onboarding is not configured');
1397
      const { connection, binding } = await resolveAuthenticatedInstallation(context);
1398
      if (binding.status !== 'connected')
1399
        throw new Error('Call openclasp_setup and obtain owner confirmation first');
1400
      return text({
1401
        agentId: binding.agent.agentId,
1402
        presence: await agentDirectory.touchAgentPresence(
1403
          connection.operatorId,
1404
          binding.agent.agentId,
1405
        ),
1406
      });
1407
    },
1408
  );
1409
  server.registerTool(
1410
    OPENCLASP_TOOL_NAMES[35],
1411
    {
1412
      title: 'Checkpoint interaction progress',
1413
      description:
1414
        '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.',
1415
      inputSchema: z
1416
        .object({
1417
          interactionId: z.string().uuid(),
1418
          sequence: z.number().int().nonnegative(),
1419
          checkpoint: ProgressCheckpointSchema,
1420
        })
1421
        .strict(),
1422
      annotations: WRITE_TOOL,
1423
    },
1424
    async (input, context) => {
1425
      if (!agentDirectory) throw new Error('Live agent sessions are not configured');
1426
      const binding = await requireBoundAgent(context);
1427
      if (!binding) throw new Error('A bound MCP installation is required');
1428
      const connection = installationContext(context);
1429
      const session = await agentDirectory.getLiveSession(
1430
        connection.operatorId,
1431
        input.interactionId,
1432
        binding.agent.agentId,
1433
      );
1434
      const result = await agentDirectory.recordLiveSessionEvent(session.reporting.bearerToken, {
1435
        eventId: crypto.randomUUID(),
1436
        interactionId: input.interactionId,
1437
        agentId: binding.agent.agentId,
1438
        sequence: input.sequence,
1439
        type: 'progress_checkpoint',
1440
        occurredAt: new Date().toISOString(),
1441
        evidenceReferences: [],
1442
        checkpoint: input.checkpoint,
1443
        details: {
1444
          labels: [`state:${input.checkpoint.state}`, `topic:${input.checkpoint.topicStatus}`],
1445
          metrics: { progress: input.checkpoint.progress, confidence: input.checkpoint.confidence },
1446
          flags: { needsHuman: input.checkpoint.needsHuman },
1447
        },
1448
      });
1449
      return text({
1450
        checkpoint: result,
1451
        ...(input.checkpoint.state === 'done' || input.checkpoint.state === 'ready_to_finalize'
1452
          ? { nextAction: 'Call openclasp_complete_live_session now.' }
1453
          : {}),
1454
      });
1455
    },
1456
  );
1457
  server.registerTool(
1458
    OPENCLASP_TOOL_NAMES[32],
1459
    {
1460
      title: 'Submit structured completion report',
1461
      description:
1462
        'Submit this agent’s structured outcome against the signed interaction contract. Raw messages and transcripts are rejected.',
1463
      inputSchema: z
1464
        .object({
1465
          interactionId: z.string().uuid(),
1466
          outcome: z.enum(['success', 'partial', 'failure', 'cancelled']),
1467
          summary: z.string().min(1).max(2000),
1468
          criteria: z
1469
            .array(
1470
              z
1471
                .object({
1472
                  criterion: z.string().min(1).max(1000),
1473
                  status: z.enum(['met', 'partially_met', 'missed', 'unknown']),
1474
                  explanation: z.string().max(1000).optional(),
1475
                  evidenceReferences: z.array(z.string().min(1).max(2048)).max(50).default([]),
1476
                })
1477
                .strict(),
1478
            )
1479
            .max(100),
1480
          deliverables: z.array(z.string().min(1).max(1000)).max(100).default([]),
1481
          actionsTaken: z.array(z.string().min(1).max(1000)).max(100).default([]),
1482
          blockers: z.array(z.string().min(1).max(1000)).max(100).default([]),
1483
          scopeChanges: z.array(z.string().min(1).max(1000)).max(100).default([]),
1484
          corrections: z.array(z.string().min(1).max(1000)).max(100).default([]),
1485
          evidenceReferences: z.array(z.string().min(1).max(2048)).max(100).default([]),
1486
          startedAt: z.string().datetime().optional(),
1487
          confidence: z.number().min(0).max(1),
1488
        })
1489
        .strict(),
1490
      annotations: WRITE_TOOL,
1491
    },
1492
    async (input, context) => {
1493
      if (!agentDirectory?.submitCompletionReport)
1494
        throw new Error('Interaction completion is not configured');
1495
      const binding = await requireBoundAgent(context);
1496
      if (!binding) throw new Error('A bound MCP installation is required');
1497
      const connection = installationContext(context);
1498
      const interaction = await agentDirectory.getFederatedInteraction(
1499
        connection.operatorId,
1500
        input.interactionId,
1501
      );
1502
      if (!interaction) throw new Error('Interaction not found');
1503
      const counterpartyAgentId =
1504
        interaction.initiatorAgentId === binding.agent.agentId
1505
          ? interaction.responderAgentId
1506
          : interaction.initiatorAgentId;
1507
      if (!interaction.contract.parties.includes(binding.agent.agentId))
1508
        throw new Error('Bound agent is not an interaction participant');
1509
      const report = InteractionCompletionReportSchema.parse({
1510
        reportId: crypto.randomUUID(),
1511
        interactionId: input.interactionId,
1512
        contractHash: interaction.termsHash,
1513
        reportingAgentId: binding.agent.agentId,
1514
        counterpartyAgentId,
1515
        agentVersion: binding.agent.agentVersion,
1516
        outcome: input.outcome,
1517
        summary: input.summary,
1518
        requestedOutcome: interaction.contract.requestedOutcome,
1519
        criteria: input.criteria,
1520
        deliverables: input.deliverables,
1521
        actionsTaken: input.actionsTaken,
1522
        blockers: input.blockers,
1523
        scopeChanges: input.scopeChanges,
1524
        corrections: input.corrections,
1525
        evidenceReferences: input.evidenceReferences,
1526
        ...(input.startedAt ? { startedAt: input.startedAt } : {}),
1527
        completedAt: new Date().toISOString(),
1528
        confidence: input.confidence,
1529
        dataSharingMode: 'structured_only',
1530
      });
1531
      return text(
1532
        await agentDirectory.submitCompletionReport(
1533
          connection.operatorId,
1534
          binding.agent.agentId,
1535
          report,
1536
          connection.credentialType === 'agent_access_token'
1537
            ? 'agent_access_token'
1538
            : 'oauth_installation',
1539
        ),
1540
      );
1541
    },
1542
  );
1543
  server.registerTool(
1544
    OPENCLASP_TOOL_NAMES[33],
1545
    {
1546
      title: 'List feedback requests',
1547
      description:
1548
        'List this agent’s pending and completed bilateral feedback requests. Peer feedback remains concealed until both respond or the timeout expires.',
1549
      inputSchema: z.object({}),
1550
      annotations: READ_ONLY_TOOL,
1551
    },
1552
    async (_input, context) => {
1553
      if (!agentDirectory?.listFeedbackRequests)
1554
        throw new Error('Interaction feedback is not configured');
1555
      const binding = await requireBoundAgent(context);
1556
      if (!binding) throw new Error('A bound MCP installation is required');
1557
      const connection = installationContext(context);
1558
      return text(
1559
        await agentDirectory.listFeedbackRequests(connection.operatorId, binding.agent.agentId),
1560
      );
1561
    },
1562
  );
1563
  server.registerTool(
1564
    OPENCLASP_TOOL_NAMES[34],
1565
    {
1566
      title: 'Submit bilateral interaction feedback',
1567
      description:
1568
        'Rate the counterparty after an interaction. The private comment is never included in the shared aggregate conclusion.',
1569
      inputSchema: z
1570
        .object({
1571
          requestId: z.string().uuid(),
1572
          interactionId: z.string().uuid(),
1573
          ratings: z
1574
            .object({
1575
              overall_satisfaction: z.number().min(0).max(1),
1576
              outcome_satisfaction: z.number().min(0).max(1),
1577
              communication: z.number().min(0).max(1),
1578
              timeliness: z.number().min(0).max(1),
1579
              scope_adherence: z.number().min(0).max(1),
1580
              evidence_quality: z.number().min(0).max(1),
1581
              correction_handling: z.number().min(0).max(1),
1582
              reliability: z.number().min(0).max(1),
1583
            })
1584
            .strict(),
1585
          wouldWorkAgain: z.enum(['yes', 'no', 'unsure']),
1586
          reasonCodes: z.array(z.string().min(1).max(128)).max(32).default([]),
1587
          privateComment: z.string().max(1000).optional(),
1588
          evidenceReferences: z.array(z.string().min(1).max(2048)).max(50).default([]),
1589
          confidence: z.number().min(0).max(1),
1590
        })
1591
        .strict(),
1592
      annotations: WRITE_TOOL,
1593
    },
1594
    async (input, context) => {
1595
      if (!agentDirectory?.submitInteractionFeedback)
1596
        throw new Error('Interaction feedback is not configured');
1597
      const binding = await requireBoundAgent(context);
1598
      if (!binding) throw new Error('A bound MCP installation is required');
1599
      const connection = installationContext(context);
1600
      const interaction = await agentDirectory.getFederatedInteraction(
1601
        connection.operatorId,
1602
        input.interactionId,
1603
      );
1604
      if (!interaction) throw new Error('Interaction not found');
1605
      const subjectAgentId =
1606
        interaction.initiatorAgentId === binding.agent.agentId
1607
          ? interaction.responderAgentId
1608
          : interaction.initiatorAgentId;
1609
      if (!interaction.contract.parties.includes(binding.agent.agentId))
1610
        throw new Error('Bound agent is not an interaction participant');
1611
      const feedback = InteractionFeedbackSchema.parse({
1612
        feedbackId: crypto.randomUUID(),
1613
        requestId: input.requestId,
1614
        interactionId: input.interactionId,
1615
        reviewerAgentId: binding.agent.agentId,
1616
        subjectAgentId,
1617
        reviewerAgentVersion: binding.agent.agentVersion,
1618
        ratings: input.ratings,
1619
        wouldWorkAgain: input.wouldWorkAgain,
1620
        reasonCodes: input.reasonCodes,
1621
        ...(input.privateComment ? { privateComment: input.privateComment } : {}),
1622
        evidenceReferences: input.evidenceReferences,
1623
        confidence: input.confidence,
1624
        submittedAt: new Date().toISOString(),
1625
      });
1626
      return text(
1627
        await agentDirectory.submitInteractionFeedback(
1628
          connection.operatorId,
1629
          binding.agent.agentId,
1630
          feedback,
1631
          connection.credentialType === 'agent_access_token'
1632
            ? 'agent_access_token'
1633
            : 'oauth_installation',
1634
        ),
1635
      );
1636
    },
1637
  );
1638
  server.registerTool(
1639
    OPENCLASP_TOOL_NAMES[36],
1640
    {
1641
      title: 'Resolve agent reference',
1642
      description:
1643
        'Resolve an OpenClasp profile URL, card URL, A2A card URL, public slug, or agent ID to one verified published agent.',
1644
      inputSchema: z.object({ reference: z.string().trim().min(1).max(2048) }),
1645
      annotations: { ...READ_ONLY_TOOL, openWorldHint: true },
1646
    },
1647
    async (input, context) => {
1648
      await requireBoundAgent(context);
1649
      if (!agentDirectory) throw new Error('The shared agent directory is not configured');
1650
      const resolved = agentDirectory.resolveAgentReference
1651
        ? await agentDirectory.resolveAgentReference(input.reference)
1652
        : await agentDirectory.getPublishedAgent(input.reference);
1653
      if (!resolved) throw new Error('Published agent reference was not found');
1654
      return text(resolved);
1655
    },
1656
  );
1657
  server.registerTool(
1658
    OPENCLASP_TOOL_NAMES[37],
1659
    {
1660
      title: 'Propose contract revision',
1661
      description:
1662
        '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.',
1663
      inputSchema: z.object({
1664
        interactionId: z.string().uuid(),
1665
        expectedTermsHash: z.string().min(1).optional(),
1666
        contract: InteractionContractSchema,
1667
      }),
1668
      annotations: WRITE_TOOL,
1669
    },
1670
    async (input, context) => {
1671
      if (!agentDirectory?.proposeContractRevision)
1672
        throw new Error('Contract negotiation is not configured');
1673
      const binding = await requireBoundAgent(context);
1674
      if (!binding) throw new Error('A bound MCP installation is required');
1675
      if (input.contract.interactionId !== input.interactionId)
1676
        throw new Error('Contract interaction ID does not match');
1677
      const connection = installationContext(context);
1678
      return text(
1679
        await agentDirectory.proposeContractRevision(
1680
          connection.operatorId,
1681
          input.interactionId,
1682
          binding.agent.agentId,
1683
          input.contract,
1684
          input.expectedTermsHash,
1685
          'oauth_installation',
1686
        ),
1687
      );
1688
    },
1689
  );
1690
  server.registerTool(
1691
    OPENCLASP_TOOL_NAMES[38],
1692
    {
1693
      title: 'Respond to contract revision',
1694
      description:
1695
        'Accept or reject the current contract proposal or amendment as the authenticated agent. Bilateral acceptance creates a platform-attested revision.',
1696
      inputSchema: z.object({
1697
        interactionId: z.string().uuid(),
1698
        revisionId: z.string().uuid(),
1699
        decision: z.enum(['accept', 'reject']),
1700
      }),
1701
      annotations: WRITE_TOOL,
1702
    },
1703
    async (input, context) => {
1704
      if (!agentDirectory?.respondToContractRevision)
1705
        throw new Error('Contract negotiation is not configured');
1706
      const binding = await requireBoundAgent(context);
1707
      if (!binding) throw new Error('A bound MCP installation is required');
1708
      const connection = installationContext(context);
1709
      return text(
1710
        await agentDirectory.respondToContractRevision(
1711
          connection.operatorId,
1712
          input.interactionId,
1713
          binding.agent.agentId,
1714
          input.revisionId,
1715
          input.decision,
1716
          'oauth_installation',
1717
        ),
1718
      );
1719
    },
1720
  );
1721
  server.registerTool(
1722
    OPENCLASP_TOOL_NAMES[39],
1723
    {
1724
      title: 'Get contextual reliability intelligence',
1725
      description:
1726
        'Return private task-specific reliability, confidence, trend, strengths, risks, and version confidence for a counterparty. This is not a universal trust score.',
1727
      inputSchema: z.object({
1728
        agentReference: z.string().trim().min(1).max(2048).optional(),
1729
        taskCategory: z.string().trim().min(1).max(100).optional(),
1730
      }),
1731
      annotations: READ_ONLY_TOOL,
1732
    },
1733
    async (input, context) => {
1734
      if (!agentDirectory?.listContextualIntelligence)
1735
        throw new Error('Contextual reliability intelligence is not configured');
1736
      const binding = await requireBoundAgent(context);
1737
      if (!binding) throw new Error('A bound MCP installation is required');
1738
      const connection = installationContext(context);
1739
      let agentId = binding.agent.agentId;
1740
      if (input.agentReference) {
1741
        const resolved = agentDirectory.resolveAgentReference
1742
          ? await agentDirectory.resolveAgentReference(input.agentReference)
1743
          : await agentDirectory.getPublishedAgent(input.agentReference);
1744
        const card = resolved?.card ?? resolved;
1745
        if (!card?.agentId) throw new Error('Published agent reference was not found');
1746
        agentId = String(card.agentId);
1747
      }
1748
      const summaries = await agentDirectory.listContextualIntelligence(connection.operatorId, {
1749
        agentId,
1750
        ...(input.taskCategory ? { taskCategory: input.taskCategory } : {}),
1751
      });
1752
      return text({
1753
        agentId,
1754
        summaries,
1755
        notice:
1756
          'Scores are private, task-specific derived signals. Confidence and evidence count must be considered with the score.',
1757
      });
1758
    },
1759
  );
1760
  server.registerTool(
1761
    OPENCLASP_TOOL_NAMES[40],
1762
    {
1763
      title: 'Recommend agents for a task',
1764
      description:
1765
        'Rank public agents for a task using published capability fit, presence, and this account’s private contextual history. Missing history is shown, not guessed.',
1766
      inputSchema: z.object({
1767
        taskCategory: z.string().trim().min(1).max(100),
1768
        query: z.string().trim().max(100).optional(),
1769
        limit: z.number().int().min(1).max(50).default(10),
1770
      }),
1771
      annotations: { ...READ_ONLY_TOOL, openWorldHint: true },
1772
    },
1773
    async (input, context) => {
1774
      if (!agentDirectory?.searchPersonalizedMarketplace)
1775
        throw new Error('Personalized agent recommendations are not configured');
1776
      const binding = await requireBoundAgent(context);
1777
      if (!binding) throw new Error('A bound MCP installation is required');
1778
      const connection = installationContext(context);
1779
      return text(
1780
        await agentDirectory.searchPersonalizedMarketplace(connection.operatorId, {
1781
          agentId: binding.agent.agentId,
1782
          taskCategory: input.taskCategory,
1783
          ...(input.query ? { query: input.query } : {}),
1784
          limit: input.limit,
1785
        }),
1786
      );
1787
    },
1788
  );
1789
  server.registerTool(
1790
    OPENCLASP_TOOL_NAMES[41],
1791
    {
1792
      title: 'Generate adaptive assurance probe',
1793
      description:
1794
        'Estimate success and risk, select one bounded pre-task or post-task question, recommend safeguards, and return a direct A2A data request. Up to three sequential rounds are allowed. No task conversation or chain-of-thought is requested.',
1795
      inputSchema: z
1796
        .object({
1797
          interactionId: z.string().uuid(),
1798
          phase: z.enum(['pre_task', 'post_task']),
1799
        })
1800
        .strict(),
1801
      annotations: { ...WRITE_TOOL, openWorldHint: true },
1802
    },
1803
    async (input, context) => {
1804
      if (
1805
        !agentDirectory?.getAssuranceProbeContext ||
1806
        !agentDirectory.beginAssuranceGeneration ||
1807
        !agentDirectory.finishAssuranceGeneration ||
1808
        !agentDirectory.saveAssuranceDecision
1809
      )
1810
        throw new Error('Adaptive assurance probes are not configured');
1811
      const binding = await requireBoundAgent(context);
1812
      if (!binding) throw new Error('A bound MCP installation is required');
1813
      const connection = installationContext(context);
1814
      const interaction = await agentDirectory.getFederatedInteraction(
1815
        connection.operatorId,
1816
        input.interactionId,
1817
      );
1818
      if (!interaction) throw new Error('Interaction not found');
1819
      const targetAgentId =
1820
        interaction.initiatorAgentId === binding.agent.agentId
1821
          ? interaction.responderAgentId
1822
          : interaction.responderAgentId === binding.agent.agentId
1823
            ? interaction.initiatorAgentId
1824
            : undefined;
1825
      if (!targetAgentId) throw new Error('Bound agent is not an interaction participant');
1826
      const generationContext = await agentDirectory.getAssuranceProbeContext(
1827
        connection.operatorId,
1828
        input.interactionId,
1829
        binding.agent.agentId,
1830
        targetAgentId,
1831
      );
1832
      const { decision, plan } = await generateAssuranceProbePlan(
1833
        {
1834
          operatorId: connection.operatorId,
1835
          interaction: generationContext.interaction,
1836
          phase: input.phase,
1837
          generatedForAgentId: binding.agent.agentId,
1838
          targetCard: generationContext.targetCard,
1839
          counterpartyBrief: generationContext.brief,
1840
          completionReports: generationContext.completionReports,
1841
          sessionEvents: generationContext.sessionEvents,
1842
          previousPlans: generationContext.previousPlans,
1843
          previousResponses: generationContext.previousResponses,
1844
          previousPredictions: generationContext.previousPredictions,
1845
          assuranceLearning: generationContext.assuranceLearning,
1846
        },
1847
        {
1848
          beginAssuranceGeneration: (record) => agentDirectory.beginAssuranceGeneration!(record),
1849
          finishAssuranceGeneration: (operatorId, generationId, value) =>
1850
            agentDirectory.finishAssuranceGeneration!(operatorId, generationId, value),
1851
          saveAssuranceDecision: (operatorId, decisionValue, planValue) =>
1852
            agentDirectory.saveAssuranceDecision!(operatorId, decisionValue, planValue),
1853
        },
1854
      );
1855
      const session = await agentDirectory.getLiveSession(
1856
        connection.operatorId,
1857
        input.interactionId,
1858
        binding.agent.agentId,
1859
      );
1860
      return text({
1861
        decision,
1862
        plan,
1863
        a2a: {
1864
          endpoint: session.peer.endpoint,
1865
          bearerToken: session.peer.bearerToken,
1866
          request: {
1867
            jsonrpc: '2.0',
1868
            id: crypto.randomUUID(),
1869
            method: 'message/send',
1870
            params: {
1871
              message: {
1872
                role: 'user',
1873
                parts: [{ kind: 'data', data: { kind: 'openclasp.assurance.probe', plan } }],
1874
                metadata: {
1875
                  [DEFAULT_EXTENSION_URI]: {
1876
                    interactionId: plan.interactionId,
1877
                    termsHash: plan.contractHash,
1878
                    assurancePlanId: plan.planId,
1879
                  },
1880
                },
1881
              },
1882
            },
1883
          },
1884
        },
1885
        next: 'Send this request directly to the peer A2A endpoint. The target should answer the bounded fields with openclasp_submit_assurance_response; do not send chain-of-thought or task conversation text.',
1886
      });
1887
    },
1888
  );
1889
  server.registerTool(
1890
    OPENCLASP_TOOL_NAMES[42],
1891
    {
1892
      title: 'List assurance probes',
1893
      description: 'List inbound and outbound bounded assurance probe plans for one interaction.',
1894
      inputSchema: z.object({ interactionId: z.string().uuid() }).strict(),
1895
      annotations: READ_ONLY_TOOL,
1896
    },
1897
    async (input, context) => {
1898
      if (!agentDirectory?.listAssuranceProbePlans)
1899
        throw new Error('Assurance probes are not configured');
1900
      const binding = await requireBoundAgent(context);
1901
      if (!binding) throw new Error('A bound MCP installation is required');
1902
      const connection = installationContext(context);
1903
      return text(
1904
        await agentDirectory.listAssuranceProbePlans(
1905
          connection.operatorId,
1906
          input.interactionId,
1907
          binding.agent.agentId,
1908
        ),
1909
      );
1910
    },
1911
  );
1912
  server.registerTool(
1913
    OPENCLASP_TOOL_NAMES[43],
1914
    {
1915
      title: 'Submit assurance response',
1916
      description:
1917
        'Submit this authenticated agent’s bounded response to an assurance probe received through A2A. Answers must match the plan and cannot contain transcripts or chain-of-thought.',
1918
      inputSchema: z
1919
        .object({
1920
          interactionId: z.string().uuid(),
1921
          planId: z.string().uuid(),
1922
          answers: z.array(AssuranceProbeAnswerSchema).length(1),
1923
        })
1924
        .strict(),
1925
      annotations: WRITE_TOOL,
1926
    },
1927
    async (input, context) => {
1928
      if (!agentDirectory?.listAssuranceProbePlans || !agentDirectory.submitAssuranceProbeResponse)
1929
        throw new Error('Assurance responses are not configured');
1930
      const binding = await requireBoundAgent(context);
1931
      if (!binding) throw new Error('A bound MCP installation is required');
1932
      const connection = installationContext(context);
1933
      const plans = await agentDirectory.listAssuranceProbePlans(
1934
        connection.operatorId,
1935
        input.interactionId,
1936
        binding.agent.agentId,
1937
      );
1938
      const plan = plans.find((candidate) => candidate.planId === input.planId);
1939
      if (!plan) throw new Error('Assurance probe plan not found for this agent');
1940
      const response = AssuranceProbeResponseSchema.parse({
1941
        protocolVersion: '0.1',
1942
        responseId: crypto.randomUUID(),
1943
        planId: plan.planId,
1944
        interactionId: plan.interactionId,
1945
        contractHash: plan.contractHash,
1946
        phase: plan.phase,
1947
        agentId: binding.agent.agentId,
1948
        agentVersion: binding.agent.agentVersion,
1949
        answers: input.answers,
1950
        respondedAt: new Date().toISOString(),
1951
      });
1952
      const stored = await agentDirectory.submitAssuranceProbeResponse(
1953
        connection.operatorId,
1954
        binding.agent.agentId,
1955
        response,
1956
      );
1957
      const session = await agentDirectory.getLiveSession(
1958
        connection.operatorId,
1959
        input.interactionId,
1960
        binding.agent.agentId,
1961
      );
1962
      return text({
1963
        ...stored,
1964
        a2a: {
1965
          endpoint: session.peer.endpoint,
1966
          bearerToken: session.peer.bearerToken,
1967
          request: {
1968
            jsonrpc: '2.0',
1969
            id: crypto.randomUUID(),
1970
            method: 'message/send',
1971
            params: {
1972
              message: {
1973
                role: 'agent',
1974
                parts: [
1975
                  {
1976
                    kind: 'data',
1977
                    data: { kind: 'openclasp.assurance.response', response },
1978
                  },
1979
                ],
1980
                metadata: {
1981
                  [DEFAULT_EXTENSION_URI]: {
1982
                    interactionId: response.interactionId,
1983
                    termsHash: response.contractHash,
1984
                    assurancePlanId: response.planId,
1985
                  },
1986
                },
1987
              },
1988
            },
1989
          },
1990
        },
1991
        next: 'Send this response directly to the peer A2A endpoint.',
1992
      });
1993
    },
1994
  );
1995
  server.registerTool(
1996
    OPENCLASP_TOOL_NAMES[44],
1997
    {
1998
      title: 'Get assurance comparisons',
1999
      description:
2000
        'Compare a counterparty’s authenticated pre-task claims with its later structured completion report. Unverifiable claims remain explicitly unverifiable.',
2001
      inputSchema: z.object({ interactionId: z.string().uuid() }).strict(),
2002
      annotations: READ_ONLY_TOOL,
2003
    },
2004
    async (input, context) => {
2005
      if (!agentDirectory?.listAssuranceComparisons)
2006
        throw new Error('Assurance comparisons are not configured');
2007
      const binding = await requireBoundAgent(context);
2008
      if (!binding) throw new Error('A bound MCP installation is required');
2009
      const connection = installationContext(context);
2010
      return text(
2011
        await agentDirectory.listAssuranceComparisons(
2012
          connection.operatorId,
2013
          input.interactionId,
2014
          binding.agent.agentId,
2015
        ),
2016
      );
2017
    },
2018
  );
2019
  server.registerTool(
2020
    OPENCLASP_TOOL_NAMES[45],
2021
    {
2022
      title: 'Get assurance decision brief',
2023
      description:
2024
        'Return private experimental predictions, risks, adaptive probes, safeguards, and outcome evaluations for one interaction.',
2025
      inputSchema: z.object({ interactionId: z.string().uuid() }).strict(),
2026
      annotations: READ_ONLY_TOOL,
2027
    },
2028
    async (input, context) => {
2029
      if (!agentDirectory?.getAssuranceBrief)
2030
        throw new Error('Assurance briefs are not configured');
2031
      const binding = await requireBoundAgent(context);
2032
      if (!binding) throw new Error('A bound MCP installation is required');
2033
      const connection = installationContext(context);
2034
      return text(
2035
        await agentDirectory.getAssuranceBrief(
2036
          connection.operatorId,
2037
          input.interactionId,
2038
          binding.agent.agentId,
2039
        ),
2040
      );
2041
    },
2042
  );
2043
  server.registerTool(
2044
    OPENCLASP_TOOL_NAMES[46],
2045
    {
2046
      title: 'Decide assurance safeguard',
2047
      description:
2048
        'Accept, reject, or modify an advisory safeguard. Accepted changes still require an explicit bilateral contract revision.',
2049
      inputSchema: z
2050
        .object({
2051
          interactionId: z.string().uuid(),
2052
          safeguardId: z.string().uuid(),
2053
          status: z.enum(['accepted', 'rejected', 'modified']),
2054
          decisionReason: z.string().trim().max(500).optional(),
2055
        })
2056
        .strict(),
2057
      annotations: WRITE_TOOL,
2058
    },
2059
    async (input, context) => {
2060
      if (!agentDirectory?.decideAssuranceSafeguard)
2061
        throw new Error('Assurance safeguard decisions are not configured');
2062
      const binding = await requireBoundAgent(context);
2063
      if (!binding) throw new Error('A bound MCP installation is required');
2064
      const connection = installationContext(context);
2065
      return text(
2066
        await agentDirectory.decideAssuranceSafeguard(
2067
          connection.operatorId,
2068
          input.interactionId,
2069
          binding.agent.agentId,
2070
          input.safeguardId,
2071
          input.status,
2072
          input.decisionReason,
2073
        ),
2074
      );
2075
    },
2076
  );
2077
  server.registerTool(
2078
    OPENCLASP_TOOL_NAMES[47],
2079
    {
2080
      title: 'Open a Shield case',
2081
      description:
2082
        'Open a private decision-support case before a consequential interaction or action. Raw conversation bodies are not required or stored.',
2083
      inputSchema: ShieldCaseInputSchema.omit({ agentId: true }),
2084
      annotations: WRITE_TOOL,
2085
    },
2086
    async (input, context) => {
2087
      if (!agentDirectory?.saveShieldCase) throw new Error('OpenClasp Shield is not configured');
2088
      const binding = await requireBoundAgent(context);
2089
      if (!binding) throw new Error('A bound MCP installation is required');
2090
      const connection = installationContext(context);
2091
      const caseRecord = createShieldCase({ ...input, agentId: binding.agent.agentId });
2092
      return text(await agentDirectory.saveShieldCase(connection.operatorId, caseRecord));
2093
    },
2094
  );
2095
  server.registerTool(
2096
    OPENCLASP_TOOL_NAMES[48],
2097
    {
2098
      title: 'Consult the Shield agent',
2099
      description:
2100
        'Ask Shield to review a situation and recommend defensible next steps. Fast analysis is the default for inline decisions; request deep analysis for an explicit investigation.',
2101
      inputSchema: z
2102
        .object({ caseId: z.string().uuid(), ...ShieldConsultInputSchema.shape })
2103
        .strict(),
2104
      annotations: WRITE_TOOL,
2105
    },
2106
    async (input, context) => {
2107
      if (
2108
        !agentDirectory?.getShieldCase ||
2109
        !agentDirectory.saveShieldCase ||
2110
        !agentDirectory.saveShieldConsultation ||
2111
        !agentDirectory.listShieldConsultations
2112
      )
2113
        throw new Error('OpenClasp Shield is not configured');
2114
      const binding = await requireBoundAgent(context);
2115
      if (!binding) throw new Error('A bound MCP installation is required');
2116
      const connection = installationContext(context);
2117
      const caseRecord = await agentDirectory.getShieldCase(connection.operatorId, input.caseId);
2118
      if (!caseRecord || caseRecord.agentId !== binding.agent.agentId)
2119
        throw new Error('Shield case not found');
2120
      const previous = await agentDirectory.listShieldConsultations(
2121
        connection.operatorId,
2122
        input.caseId,
2123
      );
2124
      const consultationInput = ShieldConsultInputSchema.parse({
2125
        message: input.message,
2126
        situationContext: input.situationContext,
2127
        analysisDepth: input.analysisDepth,
2128
        ...(input.proposedAction ? { proposedAction: input.proposedAction } : {}),
2129
        facts: input.facts,
2130
        evidence: input.evidence,
2131
        policies: input.policies,
2132
      });
2133
      const result = await consultShield(caseRecord, consultationInput, previous);
2134
      await agentDirectory.saveShieldConsultation(connection.operatorId, result.consultation);
2135
      await agentDirectory.saveShieldCase(connection.operatorId, result.caseRecord);
2136
      return text(result);
2137
    },
2138
  );
2139
  server.registerTool(
2140
    OPENCLASP_TOOL_NAMES[49],
2141
    {
2142
      title: 'Get a Shield case',
2143
      description:
2144
        'Get one private Shield case with its structured assessments. Raw consultation inputs are not retained.',
2145
      inputSchema: z.object({ caseId: z.string().uuid() }).strict(),
2146
      annotations: READ_ONLY_TOOL,
2147
    },
2148
    async (input, context) => {
2149
      if (!agentDirectory?.getShieldCase || !agentDirectory.listShieldConsultations)
2150
        throw new Error('OpenClasp Shield is not configured');
2151
      const binding = await requireBoundAgent(context);
2152
      if (!binding) throw new Error('A bound MCP installation is required');
2153
      const connection = installationContext(context);
2154
      const caseRecord = await agentDirectory.getShieldCase(connection.operatorId, input.caseId);
2155
      if (!caseRecord || caseRecord.agentId !== binding.agent.agentId)
2156
        throw new Error('Shield case not found');
2157
      return text({
2158
        case: caseRecord,
2159
        consultations: await agentDirectory.listShieldConsultations(
2160
          connection.operatorId,
2161
          input.caseId,
2162
        ),
2163
      });
2164
    },
2165
  );
2166
  server.registerTool(
2167
    OPENCLASP_TOOL_NAMES[50],
2168
    {
2169
      title: 'List Shield cases',
2170
      description: 'List this connected agent’s private Shield decision-support cases.',
2171
      inputSchema: z.object({}).strict(),
2172
      annotations: READ_ONLY_TOOL,
2173
    },
2174
    async (_input, context) => {
2175
      if (!agentDirectory?.listShieldCases) throw new Error('OpenClasp Shield is not configured');
2176
      const binding = await requireBoundAgent(context);
2177
      if (!binding) throw new Error('A bound MCP installation is required');
2178
      const connection = installationContext(context);
2179
      return text(
2180
        await agentDirectory.listShieldCases(connection.operatorId, binding.agent.agentId),
2181
      );
2182
    },
2183
  );
2184
  server.registerTool(
2185
    OPENCLASP_TOOL_NAMES[51],
2186
    {
2187
      title: 'Close a Shield case',
2188
      description:
2189
        'Close a Shield case with the observed result so future decision support can be evaluated against outcomes.',
2190
      inputSchema: z
2191
        .object({
2192
          caseId: z.string().uuid(),
2193
          result: ShieldOutcomeSchema.shape.result,
2194
          acceptedAdvice: z.boolean(),
2195
          actionTaken: ShieldOutcomeSchema.shape.actionTaken,
2196
          observedImpact: ShieldOutcomeSchema.shape.observedImpact,
2197
        })
2198
        .strict(),
2199
      annotations: WRITE_TOOL,
2200
    },
2201
    async (input, context) => {
2202
      if (
2203
        !agentDirectory?.getShieldCase ||
2204
        !agentDirectory.saveShieldCase ||
2205
        !agentDirectory.saveShieldOutcome
2206
      )
2207
        throw new Error('OpenClasp Shield is not configured');
2208
      const binding = await requireBoundAgent(context);
2209
      if (!binding) throw new Error('A bound MCP installation is required');
2210
      const connection = installationContext(context);
2211
      const caseRecord = await agentDirectory.getShieldCase(connection.operatorId, input.caseId);
2212
      if (!caseRecord || caseRecord.agentId !== binding.agent.agentId)
2213
        throw new Error('Shield case not found');
2214
      if (caseRecord.status === 'closed') throw new Error('Shield case is already closed');
2215
      const now = new Date().toISOString();
2216
      const outcome = ShieldOutcomeSchema.parse({
2217
        protocolVersion: '0.1',
2218
        outcomeId: crypto.randomUUID(),
2219
        caseId: input.caseId,
2220
        agentId: binding.agent.agentId,
2221
        result: input.result,
2222
        acceptedAdvice: input.acceptedAdvice,
2223
        actionTaken: input.actionTaken,
2224
        ...(input.observedImpact ? { observedImpact: input.observedImpact } : {}),
2225
        reportedBy: 'protected_agent',
2226
        createdAt: now,
2227
      });
2228
      await agentDirectory.saveShieldOutcome(connection.operatorId, outcome);
2229
      await agentDirectory.saveShieldCase(
2230
        connection.operatorId,
2231
        ShieldCaseSchema.parse({
2232
          ...caseRecord,
2233
          status: 'closed',
2234
          updatedAt: now,
2235
          closedAt: now,
2236
        }),
2237
      );
2238
      return text(outcome);
2239
    },
2240
  );
2241
  return server;
2242
}