loopctl-mcp-server 2.0.0 → 2.0.1
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.
- package/index.js +27 -6
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -12,9 +12,15 @@ import {
|
|
|
12
12
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
|
-
// HTTP helper
|
|
15
|
+
// HTTP helper — witness protocol state
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
|
|
18
|
+
// The witness protocol requires clients to echo back the last-known Signed
|
|
19
|
+
// Tree Head (STH) on every authenticated request. On the very first request
|
|
20
|
+
// we send X-Loopctl-STH-Bootstrap: true to receive the current STH without
|
|
21
|
+
// needing one already. After that we cache and send X-Loopctl-Last-Known-STH.
|
|
22
|
+
let lastKnownSTH = null;
|
|
23
|
+
|
|
18
24
|
function getBaseUrl() {
|
|
19
25
|
return (process.env.LOOPCTL_SERVER || "https://loopctl.com").replace(/\/$/, "");
|
|
20
26
|
}
|
|
@@ -43,13 +49,22 @@ async function apiCall(method, path, body, keyOverride) {
|
|
|
43
49
|
return { error: true, status: 0, body: "No API key configured. Set LOOPCTL_API_KEY, LOOPCTL_ORCH_KEY, or LOOPCTL_AGENT_KEY." };
|
|
44
50
|
}
|
|
45
51
|
|
|
52
|
+
const headers = {
|
|
53
|
+
Authorization: `Bearer ${key}`,
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
Accept: "application/json",
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Witness protocol: send cached STH or request bootstrap
|
|
59
|
+
if (lastKnownSTH) {
|
|
60
|
+
headers["X-Loopctl-Last-Known-STH"] = lastKnownSTH;
|
|
61
|
+
} else {
|
|
62
|
+
headers["X-Loopctl-STH-Bootstrap"] = "true";
|
|
63
|
+
}
|
|
64
|
+
|
|
46
65
|
const options = {
|
|
47
66
|
method,
|
|
48
|
-
headers
|
|
49
|
-
Authorization: `Bearer ${key}`,
|
|
50
|
-
"Content-Type": "application/json",
|
|
51
|
-
Accept: "application/json",
|
|
52
|
-
},
|
|
67
|
+
headers,
|
|
53
68
|
signal: AbortSignal.timeout(30_000),
|
|
54
69
|
};
|
|
55
70
|
|
|
@@ -68,6 +83,12 @@ async function apiCall(method, path, body, keyOverride) {
|
|
|
68
83
|
return { error: true, status: 0, body: `Network error: ${err.message}${cause}` };
|
|
69
84
|
}
|
|
70
85
|
|
|
86
|
+
// Witness protocol: cache the STH from response for subsequent requests
|
|
87
|
+
const sthHeader = response.headers.get("x-loopctl-current-sth");
|
|
88
|
+
if (sthHeader) {
|
|
89
|
+
lastKnownSTH = sthHeader;
|
|
90
|
+
}
|
|
91
|
+
|
|
71
92
|
if (response.status === 204) {
|
|
72
93
|
return { ok: true };
|
|
73
94
|
}
|