@aipermission/mcp 0.2.51 → 0.2.53
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 +10 -2
- package/dist/api-error.js +11 -4
- package/dist/config.js +15 -0
- package/dist/idempotency-key.js +8 -0
- package/dist/server.js +11 -11
- package/dist/vault-tools.js +2 -1
- package/package.json +2 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -86,8 +86,10 @@ does this automatically.
|
|
|
86
86
|
## Tools
|
|
87
87
|
|
|
88
88
|
`AIPERMISSION_HTTP_TIMEOUT_MS` sets the gateway request deadline (default:
|
|
89
|
-
60000 milliseconds).
|
|
90
|
-
|
|
89
|
+
60000 milliseconds; maximum: 600000). The value must be a positive base-10
|
|
90
|
+
integer; invalid configuration stops the bridge instead of silently changing
|
|
91
|
+
the deadline. It covers both response headers and the complete response body,
|
|
92
|
+
including streamed bodies. A timeout does not prove that a submitted
|
|
91
93
|
operation failed; do not retry mutations with a new idempotency key blindly.
|
|
92
94
|
If a POST response is lost or incomplete, the bridge returns
|
|
93
95
|
`status: outcome_unknown` and `code: gateway_transport_outcome_unknown`.
|
|
@@ -103,6 +105,12 @@ and `Retry-After`. Respect that delay; immediate retry loops cannot bypass the
|
|
|
103
105
|
gateway's per-workspace/token rate, persisted-running concurrency, input, or
|
|
104
106
|
atomic projected-storage limits.
|
|
105
107
|
|
|
108
|
+
The normal `npm test` command requires a full Git clone so its test-manifest
|
|
109
|
+
ratchet can compare against trusted history. A GitHub source archive has no
|
|
110
|
+
history; use `npm run test:source-archive` there to run the complete current
|
|
111
|
+
behavior suite and current-manifest checks. That archive command intentionally
|
|
112
|
+
does not claim to verify the historical anti-regression ratchet.
|
|
113
|
+
|
|
106
114
|
- `list_connector_targets`
|
|
107
115
|
- `get_connector_help`
|
|
108
116
|
- `get_connector_actions`
|
package/dist/api-error.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
const safeResultStatuses = new Set(["failed", "blocked", "stale", "declined", "canceled", "error", "outcome_unknown"]);
|
|
2
2
|
|
|
3
|
-
export function gatewayAPIError(data, httpStatus) {
|
|
3
|
+
export function gatewayAPIError(data, httpStatus, retryAfterHeader = null) {
|
|
4
4
|
const error = new Error(data?.error || `AIPermission API request failed with ${httpStatus}`);
|
|
5
5
|
if (typeof data?.code === "string" && data.code.length <= 128) error.code = data.code;
|
|
6
6
|
if (safeResultStatuses.has(data?.status)) error.resultStatus = data.status;
|
|
7
7
|
if (Number.isSafeInteger(data?.request_id) && data.request_id > 0) error.requestID = data.request_id;
|
|
8
8
|
if (typeof data?.assistant_hint === "string" && data.assistant_hint.length <= 2048) error.assistantHint = data.assistant_hint;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
9
|
+
const retryAfterSeconds = safeRetryAfterSeconds(data?.retry_after_seconds, retryAfterHeader);
|
|
10
|
+
if (retryAfterSeconds !== null) error.retryAfterSeconds = retryAfterSeconds;
|
|
12
11
|
return error;
|
|
13
12
|
}
|
|
13
|
+
|
|
14
|
+
function safeRetryAfterSeconds(bodyValue, headerValue) {
|
|
15
|
+
if (Number.isSafeInteger(bodyValue) && bodyValue >= 0 && bodyValue <= 3600) return bodyValue;
|
|
16
|
+
const raw = typeof headerValue === "string" ? headerValue.trim() : "";
|
|
17
|
+
if (!/^\d{1,4}$/.test(raw)) return null;
|
|
18
|
+
const value = Number(raw);
|
|
19
|
+
return value <= 3600 ? value : null;
|
|
20
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const defaultHTTPTimeoutMs = 60_000;
|
|
2
|
+
const maxHTTPTimeoutMs = 10 * 60_000;
|
|
3
|
+
|
|
4
|
+
export function parseHTTPTimeout(value) {
|
|
5
|
+
const raw = String(value ?? "").trim();
|
|
6
|
+
if (!raw) return defaultHTTPTimeoutMs;
|
|
7
|
+
if (!/^[1-9]\d*$/.test(raw)) {
|
|
8
|
+
throw new Error("AIPERMISSION_HTTP_TIMEOUT_MS must be a positive integer in milliseconds.");
|
|
9
|
+
}
|
|
10
|
+
const timeout = Number(raw);
|
|
11
|
+
if (!Number.isSafeInteger(timeout) || timeout > maxHTTPTimeoutMs) {
|
|
12
|
+
throw new Error(`AIPERMISSION_HTTP_TIMEOUT_MS must not exceed ${maxHTTPTimeoutMs} milliseconds.`);
|
|
13
|
+
}
|
|
14
|
+
return timeout;
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const idempotencyKeySchema = z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.refine((value) => Buffer.byteLength(value, "utf8") <= 128, "Idempotency key must not exceed 128 UTF-8 bytes.")
|
|
8
|
+
.describe("Caller-stable key used to prevent duplicate action requests.");
|
package/dist/server.js
CHANGED
|
@@ -13,6 +13,8 @@ import { z } from "zod";
|
|
|
13
13
|
import { callVaultActionSchema, listVaultItemsSchema, vaultActionRequestSchema } from "./vault-tools.js";
|
|
14
14
|
import { MCP_SERVER_INSTRUCTIONS } from "./instructions.js";
|
|
15
15
|
import { gatewayAPIError } from "./api-error.js";
|
|
16
|
+
import { parseHTTPTimeout } from "./config.js";
|
|
17
|
+
import { idempotencyKeySchema } from "./idempotency-key.js";
|
|
16
18
|
import { normalizeLocalAPIURL } from "./local-url.js";
|
|
17
19
|
import { jsonToolResult } from "./results.js";
|
|
18
20
|
|
|
@@ -25,7 +27,7 @@ try {
|
|
|
25
27
|
apiURLConfigurationError = new Error("Invalid local gateway URL configuration; update AIPERMISSION_API_URL.", { cause: error });
|
|
26
28
|
}
|
|
27
29
|
const apiToken = process.env.AIPERMISSION_API_TOKEN || "";
|
|
28
|
-
const apiTimeoutMs =
|
|
30
|
+
const apiTimeoutMs = parseHTTPTimeout(process.env.AIPERMISSION_HTTP_TIMEOUT_MS);
|
|
29
31
|
|
|
30
32
|
const server = new McpServer(
|
|
31
33
|
{
|
|
@@ -80,11 +82,7 @@ server.tool(
|
|
|
80
82
|
action_name: z.string().min(1).describe("Action name from get_connector_actions."),
|
|
81
83
|
input: z.record(z.unknown()).optional().describe("Connector-specific action input."),
|
|
82
84
|
reason: z.string().optional().describe("Why this connector action is needed."),
|
|
83
|
-
idempotency_key:
|
|
84
|
-
.string()
|
|
85
|
-
.min(1)
|
|
86
|
-
.max(128)
|
|
87
|
-
.describe("Caller-stable key that makes retries return the original request without running twice."),
|
|
85
|
+
idempotency_key: idempotencyKeySchema,
|
|
88
86
|
},
|
|
89
87
|
async ({ target_ref, action_name, input, reason, idempotency_key }) => {
|
|
90
88
|
return jsonToolResult(() =>
|
|
@@ -155,7 +153,7 @@ server.tool(
|
|
|
155
153
|
"Cancel one approval_pending Vault action request owned by this token. Running or terminal requests cannot be canceled.",
|
|
156
154
|
vaultActionRequestSchema,
|
|
157
155
|
async ({ request_id }) => {
|
|
158
|
-
return jsonToolResult(() => apiPost(`/api/mcp/vault-action-requests/${request_id}/cancel`, {}));
|
|
156
|
+
return jsonToolResult(() => apiPost(`/api/mcp/vault-action-requests/${request_id}/cancel`, {}, { requestID: request_id }));
|
|
159
157
|
},
|
|
160
158
|
);
|
|
161
159
|
|
|
@@ -168,7 +166,7 @@ async function apiGet(path) {
|
|
|
168
166
|
});
|
|
169
167
|
}
|
|
170
168
|
|
|
171
|
-
async function apiPost(path, body) {
|
|
169
|
+
async function apiPost(path, body, context = {}) {
|
|
172
170
|
return apiRequest(
|
|
173
171
|
path,
|
|
174
172
|
{
|
|
@@ -179,10 +177,11 @@ async function apiPost(path, body) {
|
|
|
179
177
|
body: JSON.stringify(body),
|
|
180
178
|
},
|
|
181
179
|
body?.idempotency_key,
|
|
180
|
+
context,
|
|
182
181
|
);
|
|
183
182
|
}
|
|
184
183
|
|
|
185
|
-
async function apiRequest(path, options, idempotencyKey) {
|
|
184
|
+
async function apiRequest(path, options, idempotencyKey, context = {}) {
|
|
186
185
|
if (apiURLConfigurationError) {
|
|
187
186
|
throw apiURLConfigurationError;
|
|
188
187
|
}
|
|
@@ -198,7 +197,7 @@ async function apiRequest(path, options, idempotencyKey) {
|
|
|
198
197
|
} catch {
|
|
199
198
|
throw new Error("Invalid local gateway request configuration; check the API URL and token.");
|
|
200
199
|
}
|
|
201
|
-
const timeout =
|
|
200
|
+
const timeout = apiTimeoutMs;
|
|
202
201
|
const controller = new AbortController();
|
|
203
202
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
204
203
|
let bodyReceived = false;
|
|
@@ -208,7 +207,7 @@ async function apiRequest(path, options, idempotencyKey) {
|
|
|
208
207
|
const data = response.status === 204 ? null : parseResponseBody(text);
|
|
209
208
|
bodyReceived = true;
|
|
210
209
|
if (!response.ok) {
|
|
211
|
-
throw gatewayAPIError(data, response.status);
|
|
210
|
+
throw gatewayAPIError(data, response.status, response.headers.get("Retry-After"));
|
|
212
211
|
}
|
|
213
212
|
return data;
|
|
214
213
|
} catch (error) {
|
|
@@ -225,6 +224,7 @@ async function apiRequest(path, options, idempotencyKey) {
|
|
|
225
224
|
uncertain.resultStatus = "outcome_unknown";
|
|
226
225
|
uncertain.code = "gateway_transport_outcome_unknown";
|
|
227
226
|
uncertain.idempotencyKey = idempotencyKey;
|
|
227
|
+
if (Number.isSafeInteger(context.requestID) && context.requestID > 0) uncertain.requestID = context.requestID;
|
|
228
228
|
uncertain.assistantHint = idempotencyKey
|
|
229
229
|
? "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."
|
|
230
230
|
: "The gateway response was lost; execution may have occurred. Inspect the original request status before repeating the operation.";
|
package/dist/vault-tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { idempotencyKeySchema } from "./idempotency-key.js";
|
|
2
3
|
|
|
3
4
|
export const listVaultItemsSchema = {
|
|
4
5
|
project_ref: z.string().min(1).optional().describe("Optional project id or slug. Omit to list every readable project."),
|
|
@@ -9,7 +10,7 @@ export const callVaultActionSchema = {
|
|
|
9
10
|
action_name: z.enum(["generate_item", "restart_session_with_environment"]),
|
|
10
11
|
input: z.record(z.unknown()).describe("Action input. Never include raw secret values."),
|
|
11
12
|
reason: z.string().min(1).describe("Why this Vault action is needed."),
|
|
12
|
-
idempotency_key:
|
|
13
|
+
idempotency_key: idempotencyKeySchema,
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export const vaultActionRequestSchema = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aipermission/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.53",
|
|
4
4
|
"mcpName": "io.github.aipermission/aipermission-mcp",
|
|
5
5
|
"description": "Local-first MCP bridge for the aipermission gateway.",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"lint": "eslint src test scripts",
|
|
34
34
|
"prepack": "npm run build",
|
|
35
35
|
"test": "npm run build && node scripts/run-tests.js",
|
|
36
|
+
"test:source-archive": "npm run build && node scripts/run-tests.js --current-only",
|
|
36
37
|
"test:windows-acl": "node scripts/run-windows-acl-tests.js",
|
|
37
38
|
"start": "node dist/cli.js",
|
|
38
39
|
"dev": "node src/cli.js"
|
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.53",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"identifier": "@aipermission/mcp",
|
|
11
|
-
"version": "0.2.
|
|
11
|
+
"version": "0.2.53",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
}
|