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

Viewing files at commit b506ef3834f7Commit detailsView latest

api/mcp.ts 76 lines · 2.5 KB · typescript
1
import type { AuthInfo } from '@modelcontextprotocol/server';
2
import { createMcpHandler, withMcpAuth } from 'mcp-handler';
3
import { MemoryAuditStore, TrustEngine } from '../packages/core/src/index.js';
4
import {
5
  OPENCLASP_MCP_INSTRUCTIONS,
6
  registerOpenClaspTools,
7
} from '../packages/mcp-server/src/server.js';
8
import { HostedRepository } from '../packages/persistence/src/hosted.js';
9
import { verifyAuth0Token } from './auth0.js';
10
11
const repository = process.env.DATABASE_URL
12
  ? new HostedRepository(process.env.DATABASE_URL)
13
  : undefined;
14
const engines = new Map<string, Promise<TrustEngine>>();
15
16
function engineFor(context: { http?: { authInfo?: { extra?: Record<string, unknown> } } }) {
17
  const operatorId =
18
    context.http?.authInfo?.extra?.operatorId ?? context.http?.authInfo?.extra?.userId;
19
  if (typeof operatorId !== 'string' || !repository) return Promise.resolve(new TrustEngine());
20
  let pending = engines.get(operatorId);
21
  if (!pending) {
22
    pending = repository.list(operatorId).then((rows) => {
23
      const store = new MemoryAuditStore();
24
      for (const row of rows) {
25
        if (row.kind !== 'interaction') store.append(row.kind, row.recordId, row.payload);
26
      }
27
      return new TrustEngine(store);
28
    });
29
    engines.set(operatorId, pending);
30
  }
31
  return pending;
32
}
33
34
const mcp = createMcpHandler(
35
  (server) =>
36
    registerOpenClaspTools(
37
      server,
38
      engineFor,
39
      repository
40
        ? (operatorId, kind, recordId, value) =>
41
            repository.upsert(operatorId, kind, recordId, value)
42
        : undefined,
43
      repository,
44
      repository,
45
    ),
46
  {
47
    serverInfo: { name: 'openclasp', version: '0.1.0' },
48
    instructions: OPENCLASP_MCP_INSTRUCTIONS,
49
  },
50
);
51
52
async function verifyToken(_request: Request, bearerToken?: string): Promise<AuthInfo | undefined> {
53
  if (!bearerToken) return undefined;
54
  const authentication = await verifyAuth0Token(bearerToken, {
55
    dashboard: false,
56
  });
57
  return {
58
    token: bearerToken,
59
    clientId: authentication.clientId,
60
    scopes: authentication.scopes,
61
    ...(typeof authentication.payload.exp === 'number'
62
      ? { expiresAt: authentication.payload.exp }
63
      : {}),
64
    extra: { operatorId: authentication.payload.sub },
65
  };
66
}
67
68
const handler = withMcpAuth(mcp, verifyToken, {
69
  required: true,
70
  resourceMetadataPath: '/.well-known/oauth-protected-resource',
71
  ...(process.env.OPENCLASP_MCP_URL
72
    ? { resourceUrl: new URL(process.env.OPENCLASP_MCP_URL).origin }
73
    : {}),
74
});
75
76
export { handler as GET, handler as POST };