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

Viewing files at commit c95a8380814fCommit detailsView latest

packages/mcp-server/src/server.ts 207 lines · 6.4 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
  FeedbackSchema,
7
  InteractionEventSchema,
8
  ReceiptSchema,
9
} from '../../protocol/src/index.js';
10
11
export const OPENCLASP_TOOL_NAMES = [
12
  'openclasp_create_identity',
13
  'openclasp_register_agent',
14
  'openclasp_get_profile',
15
  'openclasp_assess_counterparty',
16
  'openclasp_begin_interaction',
17
  'openclasp_record_event',
18
  'openclasp_check_claim',
19
  'openclasp_validate_commitment',
20
  'openclasp_suggest_resolution',
21
  'openclasp_complete_interaction',
22
  'openclasp_submit_feedback',
23
  'openclasp_raise_dispute',
24
  'openclasp_verify_receipt',
25
] as const;
26
27
const text = (value: unknown) => ({
28
  content: [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }],
29
});
30
31
export function buildMcpServer(engine = new TrustEngine()) {
32
  const server = new McpServer(
33
    { name: 'openclasp', version: '0.1.0' },
34
    {
35
      instructions:
36
        'Use OpenClasp to verify counterparties, record signed structured events, and request contextual risk decisions. Never send raw private conversations.',
37
    },
38
  );
39
  return registerOpenClaspTools(server, engine);
40
}
41
42
export function registerOpenClaspTools(server: McpServer, engine = new TrustEngine()) {
43
  const factChecker = new FixtureFactCheckProvider();
44
  const keys = new Map<string, unknown>();
45
46
  server.registerTool(
47
    OPENCLASP_TOOL_NAMES[0],
48
    {
49
      description: 'Create a local Ed25519 agent identity',
50
      inputSchema: z.object({
51
        agentId: z.string(),
52
        operatorRef: z.string(),
53
        capabilities: z.array(z.string()),
54
      }),
55
    },
56
    async (input, context) => {
57
      const authenticatedOperator = context.http?.authInfo?.extra?.operatorId;
58
      const created = createIdentity({
59
        ...input,
60
        operatorRef:
61
          typeof authenticatedOperator === 'string'
62
            ? `descope:${authenticatedOperator}`
63
            : input.operatorRef,
64
      });
65
      keys.set(input.agentId, created.keyPair);
66
      return text(created);
67
    },
68
  );
69
  server.registerTool(
70
    OPENCLASP_TOOL_NAMES[1],
71
    {
72
      description: 'Register and verify a signed agent identity',
73
      inputSchema: AgentIdentitySchema,
74
    },
75
    async (input, context) => {
76
      const authenticatedOperator = context.http?.authInfo?.extra?.operatorId;
77
      if (
78
        typeof authenticatedOperator === 'string' &&
79
        input.operatorRef !== `descope:${authenticatedOperator}`
80
      ) {
81
        throw new Error('Agent identity is not owned by the authenticated operator');
82
      }
83
      return text(engine.registerAgent(input));
84
    },
85
  );
86
  server.registerTool(
87
    OPENCLASP_TOOL_NAMES[2],
88
    {
89
      description: 'Get a task-specific behavioural profile',
90
      inputSchema: z.object({ agentId: z.string(), version: z.string(), taskCategory: z.string() }),
91
    },
92
    async (input) => text(engine.getRisk(input.agentId, input.version, input.taskCategory)),
93
  );
94
  server.registerTool(
95
    OPENCLASP_TOOL_NAMES[3],
96
    {
97
      description: 'Assess a counterparty in context',
98
      inputSchema: z.object({
99
        envelope: z.any(),
100
        action: z.string(),
101
        dataClasses: z.array(z.string()).optional(),
102
        humanApproved: z.boolean().optional(),
103
      }),
104
    },
105
    async (input) => text(engine.assess(input as any)),
106
  );
107
  server.registerTool(
108
    OPENCLASP_TOOL_NAMES[4],
109
    {
110
      description: 'Begin an interaction and return its identifier',
111
      inputSchema: z.object({
112
        purpose: z.string(),
113
        parties: z.array(z.string()),
114
        taskCategory: z.string(),
115
      }),
116
    },
117
    async (input) =>
118
      text({ interactionId: crypto.randomUUID(), status: 'contract_required', ...input }),
119
  );
120
  server.registerTool(
121
    OPENCLASP_TOOL_NAMES[5],
122
    {
123
      description: 'Validate and record a signed structured event',
124
      inputSchema: InteractionEventSchema,
125
    },
126
    async (input) => text(engine.recordEvent(input)),
127
  );
128
  server.registerTool(
129
    OPENCLASP_TOOL_NAMES[6],
130
    {
131
      description: 'Check an objective factual claim',
132
      inputSchema: z.object({ claim: z.string(), permission: z.boolean().optional() }),
133
    },
134
    async (input) => text(await factChecker.check(input.claim, input.permission)),
135
  );
136
  server.registerTool(
137
    OPENCLASP_TOOL_NAMES[7],
138
    {
139
      description: 'Check whether a commitment is represented by a signed event',
140
      inputSchema: z.object({ interactionId: z.string().uuid(), commitment: z.string() }),
141
    },
142
    async (input) =>
143
      text({
144
        valid: [...engine.events.values()].some(
145
          (event) =>
146
            event.interactionId === input.interactionId &&
147
            event.eventType === 'commitment' &&
148
            JSON.stringify(event.payload).includes(input.commitment),
149
        ),
150
      }),
151
  );
152
  server.registerTool(
153
    OPENCLASP_TOOL_NAMES[8],
154
    {
155
      description: 'Suggest attributable resolutions for a consented conflict',
156
      inputSchema: z.object({ conflictId: z.string() }),
157
    },
158
    async (input) => text(engine.conflicts.get(input.conflictId)?.possibleResolutions ?? []),
159
  );
160
  server.registerTool(
161
    OPENCLASP_TOOL_NAMES[9],
162
    { description: 'Submit a signed completion receipt', inputSchema: ReceiptSchema },
163
    async (input) => text(engine.submitReceipt(input)),
164
  );
165
  server.registerTool(
166
    OPENCLASP_TOOL_NAMES[10],
167
    { description: 'Submit receipt-backed structured feedback', inputSchema: FeedbackSchema },
168
    async (input) => text(engine.submitFeedback(input)),
169
  );
170
  server.registerTool(
171
    OPENCLASP_TOOL_NAMES[11],
172
    {
173
      description: 'Create a mutually consented dispute',
174
      inputSchema: z.object({
175
        interactionId: z.string().uuid(),
176
        issue: z.string(),
177
        participants: z.array(z.string()).min(2),
178
      }),
179
    },
180
    async (input) =>
181
      text(
182
        engine.createConflict({
183
          interactionId: input.interactionId,
184
          issue: input.issue,
185
          participants: input.participants,
186
          positions: {},
187
          evidence: [],
188
          contractClauses: [],
189
          missingInformation: [],
190
          possibleResolutions: [],
191
        }),
192
      ),
193
  );
194
  server.registerTool(
195
    OPENCLASP_TOOL_NAMES[12],
196
    { description: 'Verify a signed interaction receipt', inputSchema: ReceiptSchema },
197
    async (input) => {
198
      try {
199
        engine.submitReceipt(input);
200
        return text({ valid: true });
201
      } catch {
202
        return text({ valid: false });
203
      }
204
    },
205
  );
206
  return server;
207
}