openclaw-channel-basicops 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,37 +1,26 @@
1
- // Minimal BasicOps MCP-over-SSE client (ported from skills/basicops-mcp/scripts/basicops_mcp.py)
1
+ // Minimal BasicOps MCP-over-SSE client
2
2
  //
3
- // Notes:
4
- // - This intentionally keeps things simple: each tool call performs an SSE handshake
5
- // to obtain a message endpoint, then POSTs a single JSON-RPC request.
6
- // - For production we may want connection reuse, retries, and token refresh.
3
+ // Correct transport (BasicOps):
4
+ // - GET the SSE URL (text/event-stream) with Bearer token
5
+ // - Read until we receive a "data: <endpoint>" line
6
+ // - POST JSON-RPC to that endpoint
7
+ //
8
+ // This client is intentionally simple: one call opens one SSE stream.
7
9
  const DEFAULT_SSE_URL = "https://app.basicops.com/mcp/sse";
8
10
  const DEFAULT_PROTOCOL_VERSION = "2024-11-05";
9
- async function handshake(opts) {
10
- const initPayload = {
11
- jsonrpc: "2.0",
12
- id: 1,
13
- method: "initialize",
14
- params: {
15
- protocolVersion: opts.protocolVersion,
16
- capabilities: {},
17
- clientInfo: { name: "OpenClaw", version: "0.1" },
18
- },
19
- };
11
+ async function getMessageEndpoint(opts) {
20
12
  const res = await fetch(opts.baseUrl, {
21
- method: "POST",
13
+ method: "GET",
22
14
  headers: {
23
15
  Authorization: `Bearer ${opts.accessToken}`,
24
16
  Accept: "text/event-stream",
25
- "Content-Type": "application/json",
26
17
  "Cache-Control": "no-cache",
27
18
  },
28
- body: JSON.stringify(initPayload),
29
19
  });
30
20
  if (!res.ok || !res.body) {
31
21
  const body = await res.text().catch(() => "");
32
- throw new Error(`MCP handshake failed (${res.status}): ${body}`);
22
+ throw new Error(`MCP SSE connect failed (${res.status}): ${body}`);
33
23
  }
34
- // Read SSE lines until we see the first `data: ...` line (endpoint)
35
24
  const reader = res.body.getReader();
36
25
  const decoder = new TextDecoder();
37
26
  let buf = "";
@@ -41,13 +30,12 @@ async function handshake(opts) {
41
30
  if (done)
42
31
  break;
43
32
  buf += decoder.decode(value, { stream: true });
44
- // SSE lines end with \n. We'll parse line-by-line.
45
33
  let idx;
46
34
  while ((idx = buf.indexOf("\n")) !== -1) {
47
35
  const line = buf.slice(0, idx).trimEnd();
48
36
  buf = buf.slice(idx + 1);
49
- if (line.startsWith("data: ")) {
50
- let endpoint = line.slice("data: ".length).trim();
37
+ if (line.startsWith("data:")) {
38
+ let endpoint = line.slice("data:".length).trim();
51
39
  if (!endpoint)
52
40
  continue;
53
41
  if (endpoint.startsWith("/"))
@@ -56,16 +44,48 @@ async function handshake(opts) {
56
44
  }
57
45
  }
58
46
  }
59
- throw new Error("MCP handshake failed: no endpoint returned");
47
+ throw new Error("MCP SSE connect failed: no endpoint returned");
48
+ }
49
+ async function postJsonRpc(opts) {
50
+ const res = await fetch(opts.endpoint, {
51
+ method: "POST",
52
+ headers: {
53
+ Authorization: `Bearer ${opts.accessToken}`,
54
+ Accept: "application/json",
55
+ "Content-Type": "application/json",
56
+ },
57
+ body: JSON.stringify(opts.payload),
58
+ });
59
+ const raw = await res.text().catch(() => "");
60
+ if (!res.ok)
61
+ throw new Error(`MCP POST failed (${res.status}): ${raw}`);
62
+ try {
63
+ return raw ? JSON.parse(raw) : {};
64
+ }
65
+ catch {
66
+ throw new Error(`MCP returned non-JSON: ${raw.slice(0, 400)}`);
67
+ }
60
68
  }
61
69
  export async function callMcpTool(opts) {
62
70
  const baseUrl = opts.mcp.baseUrl || DEFAULT_SSE_URL;
63
71
  const accessToken = opts.mcp.accessToken;
64
72
  const protocolVersion = opts.mcp.protocolVersion || DEFAULT_PROTOCOL_VERSION;
65
73
  if (!accessToken) {
66
- throw new Error("Missing BasicOps MCP access token (channels.basicops.accounts.<id>.mcp.accessToken)");
74
+ throw new Error("Missing BasicOps MCP access token (plugins.entries.basicops.config.accounts.<id>.mcp.accessToken)");
67
75
  }
68
- const endpoint = await handshake({ baseUrl, accessToken, protocolVersion });
76
+ const endpoint = await getMessageEndpoint({ baseUrl, accessToken });
77
+ // Initialize (some MCP servers require this before tools/call)
78
+ const initPayload = {
79
+ jsonrpc: "2.0",
80
+ id: 1,
81
+ method: "initialize",
82
+ params: {
83
+ protocolVersion,
84
+ capabilities: {},
85
+ clientInfo: { name: "openclaw-channel-basicops", version: "0.1" },
86
+ },
87
+ };
88
+ await postJsonRpc({ endpoint, accessToken, payload: initPayload });
69
89
  const payload = {
70
90
  jsonrpc: "2.0",
71
91
  id: opts.requestId ?? 2,
@@ -75,34 +95,5 @@ export async function callMcpTool(opts) {
75
95
  arguments: opts.args ?? {},
76
96
  },
77
97
  };
78
- const res = await fetch(endpoint, {
79
- method: "POST",
80
- headers: {
81
- Authorization: `Bearer ${accessToken}`,
82
- Accept: "application/json",
83
- "Content-Type": "application/json",
84
- },
85
- body: JSON.stringify(payload),
86
- });
87
- const raw = await res.text().catch(() => "");
88
- if (!res.ok) {
89
- throw new Error(`MCP tool call failed (${res.status}): ${raw}`);
90
- }
91
- let json;
92
- try {
93
- json = raw ? JSON.parse(raw) : {};
94
- }
95
- catch {
96
- throw new Error(`MCP tool call returned non-JSON: ${raw.slice(0, 400)}`);
97
- }
98
- // BasicOps MCP may return { result: { isError: true, content: [...] } }
99
- const isError = Boolean(json?.result?.isError);
100
- if (isError) {
101
- const content = json?.result?.content;
102
- const text = Array.isArray(content)
103
- ? content.map((c) => (c && typeof c === "object" ? c.text : "")).join(" ")
104
- : "";
105
- throw new Error(`MCP tool error: ${text || "(unknown)"}`);
106
- }
107
- return json;
98
+ return postJsonRpc({ endpoint, accessToken, payload });
108
99
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-channel-basicops",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "BasicOps messaging channel connector for OpenClaw (webhooks inbound + MCP outbound)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",