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

Viewing files at commit 310519e1fe0fCommit detailsView latest

connectors/botpress/src/mcp.ts 93 lines · 3.0 KB · typescript
1
import * as sdk from '@botpress/sdk';
2
3
type Json = Record<string, any>;
4
5
const rpcResponse = (body: string): Json => {
6
  const candidates = body
7
    .split('\n')
8
    .filter((line) => line.startsWith('data:'))
9
    .map((line) => line.slice(5).trim())
10
    .filter(Boolean);
11
  const raw = candidates.at(-1) ?? body;
12
  try {
13
    return JSON.parse(raw) as Json;
14
  } catch {
15
    throw new sdk.RuntimeError('OpenClasp returned an invalid MCP response');
16
  }
17
};
18
19
export const callOpenClaspTool = async <T = Json>(
20
  baseUrl: string,
21
  token: string,
22
  name: string,
23
  args: Json = {},
24
): Promise<T> => {
25
  if (!token) throw new sdk.RuntimeError('OpenClasp pairing is not complete');
26
  const response = await fetch(`${baseUrl.replace(/\/$/, '')}/mcp`, {
27
    method: 'POST',
28
    headers: {
29
      authorization: `Bearer ${token}`,
30
      accept: 'application/json, text/event-stream',
31
      'content-type': 'application/json',
32
      'mcp-protocol-version': '2025-06-18',
33
    },
34
    body: JSON.stringify({
35
      jsonrpc: '2.0',
36
      id: crypto.randomUUID(),
37
      method: 'tools/call',
38
      params: { name, arguments: args },
39
    }),
40
  });
41
  const payload = rpcResponse(await response.text());
42
  if (!response.ok || payload.error)
43
    throw new sdk.RuntimeError(
44
      String(payload.error?.message ?? `OpenClasp MCP HTTP ${response.status}`),
45
    );
46
  const result = payload.result as Json | undefined;
47
  if (result?.isError)
48
    throw new sdk.RuntimeError(String(result.content?.[0]?.text ?? 'OpenClasp tool failed'));
49
  const text = result?.content?.find((item: Json) => item.type === 'text')?.text;
50
  if (typeof text !== 'string') return result as T;
51
  try {
52
    return JSON.parse(text) as T;
53
  } catch {
54
    return text as T;
55
  }
56
};
57
58
export const sendMcpA2ARequest = async (value: Json): Promise<boolean> => {
59
  const a2a = value.a2a as Json | undefined;
60
  const endpoint = a2a?.endpoint;
61
  const bearerToken = a2a?.bearerToken;
62
  const request = a2a?.request ?? a2a?.requestTemplate;
63
  if (typeof endpoint !== 'string' || typeof bearerToken !== 'string' || !request) return false;
64
  const interactionId =
65
    value.interaction?.interactionId ??
66
    value.plan?.interactionId ??
67
    value.response?.interactionId ??
68
    request.params?.message?.metadata?.interactionId;
69
  const startedAt = Date.now();
70
  const response = await fetch(endpoint, {
71
    method: 'POST',
72
    headers: {
73
      authorization: `Bearer ${bearerToken}`,
74
      'content-type': 'application/json',
75
      'A2A-Extensions': 'https://openclasp.dev/extensions/trust/v0.1',
76
    },
77
    body: JSON.stringify({
78
      jsonrpc: '2.0',
79
      id: crypto.randomUUID(),
80
      method: 'message/send',
81
      params: request.params ?? { message: request.message },
82
    }),
83
  });
84
  console.info('[openclasp-a2a]', {
85
    event: 'mcp_a2a_request.completed',
86
    interactionId,
87
    statusCode: response.status,
88
    durationMs: Date.now() - startedAt,
89
  });
90
  if (!response.ok)
91
    throw new sdk.RuntimeError(`Peer A2A endpoint returned HTTP ${response.status}`);
92
  return true;
93
};