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

Viewing files at commit 97e903f3fe69Commit detailsView latest

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