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

Viewing files at commit 8941e5beacddCommit detailsView latest

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