@aipermission/mcp 0.2.55 → 0.2.57
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 +6 -3
- package/dist/init.js +10 -5
- package/dist/resources/aipermission-operator/SKILL.md +8 -3
- package/dist/response-contracts.js +57 -22
- package/dist/server.js +6 -6
- package/dist/vault-tools.js +7 -2
- package/package.json +2 -2
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -234,9 +234,12 @@ gateway control result, not a recorded request. `outcome_unknown` is terminal
|
|
|
234
234
|
and means the gateway could not prove the remote outcome after interruption;
|
|
235
235
|
inspect target state or ask the operator
|
|
236
236
|
before retrying. Gateway API errors with that status retain their request id,
|
|
237
|
-
assistant hint, and bounded retry delay in the MCP error envelope.
|
|
238
|
-
responses
|
|
239
|
-
temporary paths, archive staging paths, or local upload contents.
|
|
237
|
+
assistant hint, and bounded retry delay in the MCP error envelope. File-transfer
|
|
238
|
+
queue and status responses do not include transferred file bytes, gateway
|
|
239
|
+
temporary paths, archive staging paths, or local upload contents. Explicitly
|
|
240
|
+
authorized connector read actions may return bounded content, such as S3
|
|
241
|
+
`download_object` `content_base64` or SSH command output; treat it as sensitive
|
|
242
|
+
target data.
|
|
240
243
|
|
|
241
244
|
## Operator Skill
|
|
242
245
|
|
package/dist/init.js
CHANGED
|
@@ -50,12 +50,18 @@ async function runConfiguration(command, argv) {
|
|
|
50
50
|
const flags = parseCommandFlags(command, argv);
|
|
51
51
|
const interactive = Boolean(input.isTTY && output.isTTY);
|
|
52
52
|
assertProviderSelectionAvailable(flags.provider, interactive);
|
|
53
|
-
|
|
53
|
+
let rl;
|
|
54
|
+
const getReadline = () => {
|
|
55
|
+
rl ||= readline.createInterface({ input, output });
|
|
56
|
+
return rl;
|
|
57
|
+
};
|
|
54
58
|
try {
|
|
55
59
|
const provider = flags.provider
|
|
56
60
|
? findProvider(flags.provider)
|
|
57
61
|
: await selectProvider("Which AI client should use this token?", MCP_PROVIDERS);
|
|
58
|
-
const
|
|
62
|
+
const needsToken = provider.id !== "custom" && !flags.print;
|
|
63
|
+
const stdinToken = needsToken && flags.tokenStdin ? (await readStdin()).trim() : "";
|
|
64
|
+
const name = sanitizeName(flags.name || (interactive ? await ask(getReadline(), "MCP server name", "aipermission") : "aipermission"));
|
|
59
65
|
const apiUrl = normalizeURL(flags.apiUrl || DEFAULT_API_URL);
|
|
60
66
|
const outputTarget =
|
|
61
67
|
provider.id === "custom"
|
|
@@ -75,8 +81,7 @@ async function runConfiguration(command, argv) {
|
|
|
75
81
|
return { provider: provider.id, name, printed: true, skill: skillResult };
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
const
|
|
79
|
-
const token = await resolveToken({ ...flags, stdinToken }, rl);
|
|
84
|
+
const token = flags.tokenStdin ? stdinToken : await resolveToken(flags, getReadline());
|
|
80
85
|
if (!token) {
|
|
81
86
|
throw new Error("API token is required.");
|
|
82
87
|
}
|
|
@@ -103,7 +108,7 @@ async function runConfiguration(command, argv) {
|
|
|
103
108
|
console.log(`${color.yellow}Restart the AI client so it reloads MCP servers.${color.reset}`);
|
|
104
109
|
return { provider: provider.id, name, config: result, skill: skillResult };
|
|
105
110
|
} finally {
|
|
106
|
-
rl
|
|
111
|
+
rl?.close();
|
|
107
112
|
}
|
|
108
113
|
}
|
|
109
114
|
|
|
@@ -113,6 +113,7 @@ Terminal statuses:
|
|
|
113
113
|
```text
|
|
114
114
|
completed
|
|
115
115
|
failed
|
|
116
|
+
canceled
|
|
116
117
|
declined
|
|
117
118
|
blocked
|
|
118
119
|
error
|
|
@@ -193,8 +194,11 @@ files or inspect remote paths. Prefer the smallest explicit path set. Do not use
|
|
|
193
194
|
globs, recursive copy, or directory transfer unless a connector action
|
|
194
195
|
explicitly supports that behavior.
|
|
195
196
|
|
|
196
|
-
|
|
197
|
-
staging paths, or local upload contents.
|
|
197
|
+
File-transfer queue and status responses do not include transferred file bytes,
|
|
198
|
+
gateway temp paths, archive staging paths, or local upload contents. Explicitly
|
|
199
|
+
authorized connector read actions may return bounded content, such as S3
|
|
200
|
+
`download_object` `content_base64` or SSH command output; treat it as sensitive
|
|
201
|
+
target data.
|
|
198
202
|
|
|
199
203
|
## S3/Object Storage Practice
|
|
200
204
|
|
|
@@ -259,7 +263,8 @@ the operator how to rotate or redact it.
|
|
|
259
263
|
Use Project Vault only through its dedicated tools:
|
|
260
264
|
|
|
261
265
|
1. Call `list_vault_items(project_ref)` to discover names and non-secret
|
|
262
|
-
metadata.
|
|
266
|
+
metadata. Prefer an explicit `id:<id>` or `slug:<slug>` reference. Never ask
|
|
267
|
+
the gateway to reveal values.
|
|
263
268
|
2. Use `call_vault_action` with `generate_item` only when the operator asked for
|
|
264
269
|
a new secret. Use a stable, unique `idempotency_key`. Send `tags` as a string
|
|
265
270
|
array, `shared_project_ids` as an integer array, and `usage_notes` as an
|
|
@@ -237,24 +237,46 @@ const vaultSessionOutputSchema = z
|
|
|
237
237
|
})
|
|
238
238
|
.strict();
|
|
239
239
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
240
|
+
const vaultActionStatuses = ["approval_pending", "running", "completed", "failed", "declined", "stale", "canceled", "expired"];
|
|
241
|
+
const vaultActionRecordedShape = {
|
|
242
|
+
status: z.enum(vaultActionStatuses),
|
|
243
|
+
request_id: positiveID,
|
|
244
|
+
project_ref: z.string().min(1),
|
|
245
|
+
reason: z.string().optional(),
|
|
246
|
+
created_at: z.string().optional(),
|
|
247
|
+
expires_at: z.string().optional(),
|
|
248
|
+
secret_values_returned: z.literal(false),
|
|
249
|
+
output_withheld: z.literal(true).optional(),
|
|
250
|
+
retry_after_seconds: nonNegativeInteger.max(3600).optional(),
|
|
251
|
+
assistant_hint: z.string().optional(),
|
|
252
|
+
error: z.string().optional(),
|
|
253
|
+
};
|
|
254
|
+
const vaultActionRecordedSchema = z
|
|
255
|
+
.discriminatedUnion("action_name", [
|
|
256
|
+
z
|
|
257
|
+
.object({
|
|
258
|
+
...vaultActionRecordedShape,
|
|
259
|
+
action_name: z.literal("generate_item"),
|
|
260
|
+
input: vaultGenerateInputSchema.optional(),
|
|
261
|
+
output: generatedVaultOutputSchema.optional(),
|
|
262
|
+
})
|
|
263
|
+
.strict(),
|
|
264
|
+
z
|
|
265
|
+
.object({
|
|
266
|
+
...vaultActionRecordedShape,
|
|
267
|
+
action_name: z.literal("restart_session_with_environment"),
|
|
268
|
+
input: vaultSessionInputSchema.optional(),
|
|
269
|
+
output: vaultSessionOutputSchema.optional(),
|
|
270
|
+
})
|
|
271
|
+
.strict(),
|
|
272
|
+
])
|
|
273
|
+
.superRefine((value, context) => {
|
|
274
|
+
if (value.output_withheld && value.output !== undefined) {
|
|
275
|
+
context.addIssue({ code: z.ZodIssueCode.custom, message: "withheld Vault output must not contain result content" });
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
const vaultActionStoppedSchema = z.object({ status: z.literal("stopped"), error: z.string().min(1) }).strict();
|
|
279
|
+
const vaultActionResponseSchema = z.union([vaultActionRecordedSchema, vaultActionStoppedSchema]);
|
|
258
280
|
|
|
259
281
|
export const responseContracts = Object.freeze({
|
|
260
282
|
connectorTargets: z.array(connectorTargetSchema),
|
|
@@ -266,12 +288,25 @@ export const responseContracts = Object.freeze({
|
|
|
266
288
|
vaultAction: vaultActionResponseSchema,
|
|
267
289
|
});
|
|
268
290
|
|
|
269
|
-
export function projectGatewaySuccess(schema, value) {
|
|
291
|
+
export function projectGatewaySuccess(schema, value, expected = undefined) {
|
|
270
292
|
const result = schema.safeParse(value);
|
|
271
293
|
if (!result.success) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
294
|
+
throw gatewayContractError();
|
|
295
|
+
}
|
|
296
|
+
if (expected && result.data?.status !== "stopped") {
|
|
297
|
+
for (const [field, expectedValue] of Object.entries(expected)) {
|
|
298
|
+
if (expectedValue !== undefined && result.data?.[field] !== expectedValue) {
|
|
299
|
+
throw gatewayContractError();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
} else if (expected && (expected.request_id !== undefined || expected.status !== undefined)) {
|
|
303
|
+
throw gatewayContractError();
|
|
275
304
|
}
|
|
276
305
|
return result.data;
|
|
277
306
|
}
|
|
307
|
+
|
|
308
|
+
function gatewayContractError() {
|
|
309
|
+
const error = new Error("Gateway success response failed MCP contract validation.");
|
|
310
|
+
error.code = "gateway_response_contract_invalid";
|
|
311
|
+
return error;
|
|
312
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -113,7 +113,7 @@ server.tool(
|
|
|
113
113
|
},
|
|
114
114
|
{ signal },
|
|
115
115
|
),
|
|
116
|
-
(value) => projectGatewaySuccess(responseContracts.connectorActionCall, value),
|
|
116
|
+
(value) => projectGatewaySuccess(responseContracts.connectorActionCall, value, { target_ref, action_name }),
|
|
117
117
|
{ idempotencyKey: idempotency_key },
|
|
118
118
|
);
|
|
119
119
|
},
|
|
@@ -129,7 +129,7 @@ server.tool(
|
|
|
129
129
|
async ({ request_id }, { signal }) => {
|
|
130
130
|
return jsonToolResult(
|
|
131
131
|
() => apiGet(`/api/mcp/connector-action-requests/${request_id}`, { signal }),
|
|
132
|
-
(value) => projectGatewaySuccess(responseContracts.connectorActionRequest, value),
|
|
132
|
+
(value) => projectGatewaySuccess(responseContracts.connectorActionRequest, value, { request_id }),
|
|
133
133
|
);
|
|
134
134
|
},
|
|
135
135
|
);
|
|
@@ -154,7 +154,7 @@ server.tool(
|
|
|
154
154
|
|
|
155
155
|
server.tool(
|
|
156
156
|
"call_vault_action",
|
|
157
|
-
"Run a Vault action under the configured project capability. Prompt waits for local approval; Always executes immediately through the same tracked request path. generate_item input accepts name, secret_type, generator_kind, provider, environment, description, expires_at, expiry_warning_days, tags (string array), usage_notes (array of {location, notes}), and shared_project_ids (integer array). restart_session_with_environment input requires target_ref and items with item_id, source_project_id, and optional replace_existing. Never include raw secret values.",
|
|
157
|
+
"Run a Vault action under the configured project capability. Prompt waits for local approval; Always executes immediately through the same tracked request path. generate_item input accepts name, optional secret_type (defaults to generic_secret), generator_kind, provider, environment, description, expires_at, expiry_warning_days, tags (string array), usage_notes (array of {location, notes}), and shared_project_ids (integer array). restart_session_with_environment input requires target_ref and items with item_id, source_project_id, and optional replace_existing. Never include raw secret values.",
|
|
158
158
|
callVaultActionSchema,
|
|
159
159
|
externalActionAnnotations,
|
|
160
160
|
async ({ project_ref, action_name, input, reason, idempotency_key }, { signal }) => {
|
|
@@ -171,7 +171,7 @@ server.tool(
|
|
|
171
171
|
},
|
|
172
172
|
{ signal },
|
|
173
173
|
),
|
|
174
|
-
(value) => projectGatewaySuccess(responseContracts.vaultAction, value),
|
|
174
|
+
(value) => projectGatewaySuccess(responseContracts.vaultAction, value, { project_ref, action_name }),
|
|
175
175
|
{ idempotencyKey: idempotency_key },
|
|
176
176
|
);
|
|
177
177
|
},
|
|
@@ -185,7 +185,7 @@ server.tool(
|
|
|
185
185
|
async ({ request_id }, { signal }) => {
|
|
186
186
|
return jsonToolResult(
|
|
187
187
|
() => apiGet(`/api/mcp/vault-action-requests/${request_id}`, { signal }),
|
|
188
|
-
(value) => projectGatewaySuccess(responseContracts.vaultAction, value),
|
|
188
|
+
(value) => projectGatewaySuccess(responseContracts.vaultAction, value, { request_id }),
|
|
189
189
|
);
|
|
190
190
|
},
|
|
191
191
|
);
|
|
@@ -198,7 +198,7 @@ server.tool(
|
|
|
198
198
|
async ({ request_id }, { signal }) => {
|
|
199
199
|
return jsonToolResult(
|
|
200
200
|
() => apiPost(`/api/mcp/vault-action-requests/${request_id}/cancel`, {}, { requestID: request_id, signal }),
|
|
201
|
-
(value) => projectGatewaySuccess(responseContracts.vaultAction, value),
|
|
201
|
+
(value) => projectGatewaySuccess(responseContracts.vaultAction, value, { request_id, status: "canceled" }),
|
|
202
202
|
{ requestID: request_id },
|
|
203
203
|
);
|
|
204
204
|
},
|
package/dist/vault-tools.js
CHANGED
|
@@ -2,11 +2,16 @@ import { z } from "zod";
|
|
|
2
2
|
import { idempotencyKeySchema } from "./idempotency-key.js";
|
|
3
3
|
|
|
4
4
|
export const listVaultItemsSchema = {
|
|
5
|
-
project_ref: z
|
|
5
|
+
project_ref: z
|
|
6
|
+
.string()
|
|
7
|
+
.trim()
|
|
8
|
+
.min(1)
|
|
9
|
+
.optional()
|
|
10
|
+
.describe("Optional project reference. Prefer id:<id> or slug:<slug>; omit to list every readable project."),
|
|
6
11
|
};
|
|
7
12
|
|
|
8
13
|
export const callVaultActionSchema = {
|
|
9
|
-
project_ref: z.string().min(1).describe("Owning project id or slug
|
|
14
|
+
project_ref: z.string().trim().min(1).describe("Owning project reference. Prefer id:<id> or slug:<slug>."),
|
|
10
15
|
action_name: z.enum(["generate_item", "restart_session_with_environment"]),
|
|
11
16
|
input: z.record(z.unknown()).describe("Action input. Never include raw secret values."),
|
|
12
17
|
reason: z.string().min(1).describe("Why this Vault action is needed."),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aipermission/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.57",
|
|
4
4
|
"mcpName": "io.github.aipermission/aipermission-mcp",
|
|
5
5
|
"description": "Local-only MCP bridge for the aipermission gateway.",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -58,6 +58,6 @@
|
|
|
58
58
|
"@eslint/js": "^10.0.1",
|
|
59
59
|
"eslint": "^10.10.0",
|
|
60
60
|
"globals": "^17.11.0",
|
|
61
|
-
"prettier": "^3.9.
|
|
61
|
+
"prettier": "^3.9.8"
|
|
62
62
|
}
|
|
63
63
|
}
|
package/server.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"name": "io.github.aipermission/aipermission-mcp",
|
|
4
4
|
"title": "AIPermission",
|
|
5
5
|
"description": "Local-only MCP bridge for the AIPermission gateway.",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.57",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"identifier": "@aipermission/mcp",
|
|
11
|
-
"version": "0.2.
|
|
11
|
+
"version": "0.2.57",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
}
|