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

Viewing files at commit 28cda791c50eCommit detailsView latest

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