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

Viewing files at commit e4047161f2b3Commit detailsView latest

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