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

Viewing files at commit 89b58109a0e6Commit detailsView latest

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