@aipermission/mcp 0.2.43 → 0.2.46
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/README.md +14 -0
- package/dist/results.js +3 -0
- package/dist/server.js +79 -35
- package/package.json +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -85,6 +85,20 @@ does this automatically.
|
|
|
85
85
|
|
|
86
86
|
## Tools
|
|
87
87
|
|
|
88
|
+
`AIPERMISSION_HTTP_TIMEOUT_MS` sets the gateway request deadline (default:
|
|
89
|
+
60000 milliseconds). It covers both response headers and the complete response
|
|
90
|
+
body, including streamed bodies. A timeout does not prove that a submitted
|
|
91
|
+
operation failed; do not retry mutations with a new idempotency key blindly.
|
|
92
|
+
If a POST response is lost or incomplete, the bridge returns
|
|
93
|
+
`status: outcome_unknown` and `code: gateway_transport_outcome_unknown`.
|
|
94
|
+
Action calls also return the original `idempotency_key`; no request ID is
|
|
95
|
+
invented. Reconcile using that same key and unchanged input, not a new logical
|
|
96
|
+
operation. Known gateway errors retain their original status and request ID.
|
|
97
|
+
Local input-validation failures do not imply that execution occurred.
|
|
98
|
+
Malformed or incomplete JSON responses are not treated as success. Invalid
|
|
99
|
+
local headers are rejected before dispatch without echoing the API token;
|
|
100
|
+
transport errors never echo raw response fragments or header values.
|
|
101
|
+
|
|
88
102
|
- `list_connector_targets`
|
|
89
103
|
- `get_connector_help`
|
|
90
104
|
- `get_connector_actions`
|
package/dist/results.js
CHANGED
|
@@ -16,6 +16,8 @@ export function errorResult(error) {
|
|
|
16
16
|
const status = error instanceof Error && typeof error.resultStatus === "string" ? error.resultStatus : "error";
|
|
17
17
|
const requestID = error instanceof Error && Number.isSafeInteger(error.requestID) && error.requestID > 0 ? error.requestID : null;
|
|
18
18
|
const assistantHint = error instanceof Error && typeof error.assistantHint === "string" ? error.assistantHint : "";
|
|
19
|
+
const idempotencyKey =
|
|
20
|
+
error instanceof Error && typeof error.idempotencyKey === "string" && error.idempotencyKey.length <= 128 ? error.idempotencyKey : "";
|
|
19
21
|
const retryAfterSeconds =
|
|
20
22
|
error instanceof Error && Number.isSafeInteger(error.retryAfterSeconds) && error.retryAfterSeconds >= 0
|
|
21
23
|
? error.retryAfterSeconds
|
|
@@ -31,6 +33,7 @@ export function errorResult(error) {
|
|
|
31
33
|
...(code ? { code } : {}),
|
|
32
34
|
...(requestID ? { request_id: requestID } : {}),
|
|
33
35
|
...(assistantHint ? { assistant_hint: assistantHint } : {}),
|
|
36
|
+
...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),
|
|
34
37
|
...(retryAfterSeconds !== null ? { retry_after_seconds: retryAfterSeconds } : {}),
|
|
35
38
|
error: message,
|
|
36
39
|
},
|
package/dist/server.js
CHANGED
|
@@ -163,60 +163,104 @@ async function apiGet(path) {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
async function apiPost(path, body) {
|
|
166
|
-
return apiRequest(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
return apiRequest(
|
|
167
|
+
path,
|
|
168
|
+
{
|
|
169
|
+
method: "POST",
|
|
170
|
+
headers: {
|
|
171
|
+
"Content-Type": "application/json",
|
|
172
|
+
},
|
|
173
|
+
body: JSON.stringify(body),
|
|
170
174
|
},
|
|
171
|
-
body
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async function apiRequest(path, options) {
|
|
176
|
-
const response = await apiFetch(path, options);
|
|
177
|
-
const text = await response.text();
|
|
178
|
-
const data = parseResponseBody(text);
|
|
179
|
-
if (!response.ok) {
|
|
180
|
-
throw gatewayAPIError(data, response.status);
|
|
181
|
-
}
|
|
182
|
-
return data;
|
|
175
|
+
body?.idempotency_key,
|
|
176
|
+
);
|
|
183
177
|
}
|
|
184
178
|
|
|
185
|
-
async function
|
|
179
|
+
async function apiRequest(path, options, idempotencyKey) {
|
|
186
180
|
if (!apiToken) {
|
|
187
181
|
throw new Error("AIPERMISSION_API_TOKEN is required.");
|
|
188
182
|
}
|
|
183
|
+
let request;
|
|
184
|
+
try {
|
|
185
|
+
request = new Request(`${apiUrl}${path}`, {
|
|
186
|
+
...options,
|
|
187
|
+
headers: new Headers({ Authorization: `Bearer ${apiToken}`, ...(options.headers || {}) }),
|
|
188
|
+
});
|
|
189
|
+
} catch {
|
|
190
|
+
throw new Error("Invalid local gateway request configuration; check the API URL and token.");
|
|
191
|
+
}
|
|
189
192
|
const timeout = Number.isFinite(apiTimeoutMs) && apiTimeoutMs > 0 ? apiTimeoutMs : 60000;
|
|
190
193
|
const controller = new AbortController();
|
|
191
194
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
192
|
-
let
|
|
195
|
+
let bodyReceived = false;
|
|
193
196
|
try {
|
|
194
|
-
response = await fetch(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
const response = await fetch(request, { signal: controller.signal });
|
|
198
|
+
const text = await response.text();
|
|
199
|
+
const data = response.status === 204 ? null : parseResponseBody(text);
|
|
200
|
+
bodyReceived = true;
|
|
201
|
+
if (!response.ok) {
|
|
202
|
+
throw gatewayAPIError(data, response.status);
|
|
203
|
+
}
|
|
204
|
+
return data;
|
|
202
205
|
} catch (error) {
|
|
203
|
-
|
|
204
|
-
|
|
206
|
+
let failure = bodyReceived ? error : new Error("Gateway response unavailable or invalid.", { cause: error });
|
|
207
|
+
if (controller.signal.aborted) {
|
|
208
|
+
failure = new Error(`AIPermission API request timed out after ${timeout}ms`, { cause: error });
|
|
209
|
+
}
|
|
210
|
+
const definitelyNotDispatched = !bodyReceived && isDefinitePredispatchTransportError(error);
|
|
211
|
+
if (definitelyNotDispatched) {
|
|
212
|
+
throw new Error("AIPermission gateway connection failed before request dispatch.", { cause: error });
|
|
205
213
|
}
|
|
206
|
-
|
|
214
|
+
if (options.method === "POST" && !bodyReceived) {
|
|
215
|
+
const uncertain = new Error(failure.message || "Gateway response unavailable", { cause: failure });
|
|
216
|
+
uncertain.resultStatus = "outcome_unknown";
|
|
217
|
+
uncertain.code = "gateway_transport_outcome_unknown";
|
|
218
|
+
uncertain.idempotencyKey = idempotencyKey;
|
|
219
|
+
uncertain.assistantHint = idempotencyKey
|
|
220
|
+
? "The gateway response was lost; execution may have occurred. Reconcile the original request using the same idempotency key and unchanged input. Never retry with a new key blindly."
|
|
221
|
+
: "The gateway response was lost; execution may have occurred. Inspect the original request status before repeating the operation.";
|
|
222
|
+
throw uncertain;
|
|
223
|
+
}
|
|
224
|
+
throw failure;
|
|
207
225
|
} finally {
|
|
208
226
|
clearTimeout(timer);
|
|
209
227
|
}
|
|
210
|
-
return response;
|
|
211
228
|
}
|
|
212
229
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
230
|
+
const definitePredispatchErrorCodes = new Set([
|
|
231
|
+
"ECONNREFUSED",
|
|
232
|
+
"ENETUNREACH",
|
|
233
|
+
"EHOSTUNREACH",
|
|
234
|
+
"ENOTFOUND",
|
|
235
|
+
"EAI_AGAIN",
|
|
236
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
237
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
238
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
239
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
240
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
241
|
+
"CERT_HAS_EXPIRED",
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
function isDefinitePredispatchTransportError(error) {
|
|
245
|
+
const pending = [error];
|
|
246
|
+
const visited = new Set();
|
|
247
|
+
while (pending.length > 0) {
|
|
248
|
+
const current = pending.pop();
|
|
249
|
+
if (!current || (typeof current !== "object" && typeof current !== "function") || visited.has(current)) continue;
|
|
250
|
+
visited.add(current);
|
|
251
|
+
if (definitePredispatchErrorCodes.has(current.code)) return true;
|
|
252
|
+
if (current.cause) pending.push(current.cause);
|
|
253
|
+
if (Array.isArray(current.errors)) pending.push(...current.errors);
|
|
216
254
|
}
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function parseResponseBody(text) {
|
|
217
259
|
try {
|
|
218
|
-
|
|
260
|
+
const data = JSON.parse(text);
|
|
261
|
+
if (!data || typeof data !== "object") throw new Error("Invalid response shape");
|
|
262
|
+
return data;
|
|
219
263
|
} catch {
|
|
220
|
-
|
|
264
|
+
throw new Error("Gateway returned an invalid JSON response.");
|
|
221
265
|
}
|
|
222
266
|
}
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"name": "io.github.aipermission/aipermission-mcp",
|
|
4
4
|
"title": "AIPermission",
|
|
5
5
|
"description": "Local-first MCP bridge for the AIPermission gateway.",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.46",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"identifier": "@aipermission/mcp",
|
|
11
|
-
"version": "0.2.
|
|
11
|
+
"version": "0.2.46",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
}
|