@aipermission/mcp 0.1.9 → 0.1.13
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 -2
- package/dist/resources/aipermission-operator/SKILL.md +45 -7
- package/dist/server.js +69 -18
- package/package.json +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -70,6 +70,10 @@ The generated MCP config contains a bearer token. Keep it private. For project-l
|
|
|
70
70
|
|
|
71
71
|
`exec` is intended for non-interactive commands. The gateway closes stdin for MCP command bodies so stdin-reading commands cannot consume the internal shell wrapper. Use the web console for interactive work.
|
|
72
72
|
|
|
73
|
+
Command responses can include `policy_warnings` for common high-risk command
|
|
74
|
+
patterns. They are best-effort safety rails and do not replace token
|
|
75
|
+
permissions, approval review, or operator judgment.
|
|
76
|
+
|
|
73
77
|
File transfer tools are intentionally conservative. MCP can list transfer
|
|
74
78
|
metadata, browse remote directories, start remote download queues, save
|
|
75
79
|
completed downloads to explicit local paths, upload explicit local files, and
|
|
@@ -98,11 +102,11 @@ Supported clients:
|
|
|
98
102
|
- `gemini`: `GEMINI.md`
|
|
99
103
|
- `custom`: prints portable Markdown to stdout
|
|
100
104
|
|
|
101
|
-
These instructions teach the agent how to poll `approval_pending` and `running` requests, handle `stale` approvals by sending a fresh request, read live console output, write short reasons, use explicit file transfer paths, and avoid printing secrets. The default installer uses the operator instruction bundled in the npm package; `--source` accepts local file paths only and rejects HTTP(S) sources.
|
|
105
|
+
These instructions teach the agent how to poll `approval_pending` and `running` requests, handle `stale` approvals by sending a fresh request, read live console output, recover stuck persistent console sessions with `restart_console_session`, write short reasons, use explicit file transfer paths, and avoid printing secrets. The default installer uses the operator instruction bundled in the npm package; `--source` accepts local file paths only and rejects HTTP(S) sources.
|
|
102
106
|
|
|
103
107
|
## Security Boundary
|
|
104
108
|
|
|
105
|
-
This package talks to a local AIPermission gateway. `AIPERMISSION_API_URL` must point to `localhost`, `127.0.0.1`, or `[::1]`; remote URLs are rejected before the bearer token is sent. Do not expose the gateway on LAN or the public internet, and do not use it as a shared DevOps service. Tokens grant access only to the servers and execution rules configured in the gateway UI. Token/server permissions may be temporary; expired grants are omitted from `list_servers` and no longer authorize command, console, or file-transfer tools.
|
|
109
|
+
This package talks to a local AIPermission gateway. `AIPERMISSION_API_URL` must point to `localhost`, `127.0.0.1`, or `[::1]`; remote URLs are rejected before the bearer token is sent. Do not expose the gateway on LAN or the public internet, and do not use it as a shared DevOps service. Tokens grant access only to the servers and execution rules configured in the gateway UI. Token/server permissions may be temporary; expired grants are omitted from `list_servers` and no longer authorize command, console, or file-transfer tools. `list_servers` is permission-scoped, not a live SSH health check; treat `exec` connection errors as the current reachability signal.
|
|
106
110
|
|
|
107
111
|
## License
|
|
108
112
|
|
|
@@ -27,9 +27,15 @@ If no server is visible, say that the current token has no accessible servers.
|
|
|
27
27
|
If `expires_at` is present, treat access as temporary. Finish within that
|
|
28
28
|
maintenance window or ask the operator to extend access.
|
|
29
29
|
|
|
30
|
+
`list_servers()` is permission-scoped, not a live health check. A visible server
|
|
31
|
+
may still be powered off, unreachable, reject SSH authentication, or require host
|
|
32
|
+
key review. Treat `exec` dial, timeout, authentication, and host-key errors as
|
|
33
|
+
the current reachability signal.
|
|
34
|
+
|
|
30
35
|
## Command Reasons
|
|
31
36
|
|
|
32
|
-
Every `exec` call should include a short `reason`.
|
|
37
|
+
Every single-server `exec` call should include a short `reason`. Multi-server
|
|
38
|
+
`exec` calls with `server_ids` must include a short `reason`.
|
|
33
39
|
|
|
34
40
|
Good reasons:
|
|
35
41
|
|
|
@@ -47,6 +53,30 @@ debug
|
|
|
47
53
|
test
|
|
48
54
|
```
|
|
49
55
|
|
|
56
|
+
## Bulk Command Flow
|
|
57
|
+
|
|
58
|
+
Use `exec(server_ids, command, reason)` when the operator asks for the same
|
|
59
|
+
non-interactive command on multiple servers, such as checking package update
|
|
60
|
+
state, uptime, disk usage, or a service status across a small fleet.
|
|
61
|
+
|
|
62
|
+
Before using multi-server `exec`:
|
|
63
|
+
|
|
64
|
+
1. Call `list_servers()`.
|
|
65
|
+
2. Choose only the requested target servers.
|
|
66
|
+
3. Keep the command non-interactive and bounded.
|
|
67
|
+
4. Use one clear reason that applies to every target.
|
|
68
|
+
|
|
69
|
+
After multi-server `exec` returns:
|
|
70
|
+
|
|
71
|
+
1. Inspect every item.
|
|
72
|
+
2. For each item with `request_id`, poll `get_request(request_id)` until terminal.
|
|
73
|
+
3. If several `always_run` items are still running, use `read_console(server_ids, tail)` to inspect live output in one read-only call.
|
|
74
|
+
4. Treat `blocked` or unauthorized items as skipped, not failed executions.
|
|
75
|
+
5. Do not assume all targets started just because the top-level status is `accepted`.
|
|
76
|
+
|
|
77
|
+
Prefer individual `exec` calls when each server needs a different command,
|
|
78
|
+
different reason, or separate investigation path.
|
|
79
|
+
|
|
50
80
|
## Approval Flow
|
|
51
81
|
|
|
52
82
|
`approval_pending` is not terminal.
|
|
@@ -72,6 +102,13 @@ stale
|
|
|
72
102
|
|
|
73
103
|
If the request is `declined`, read `user_note` and follow the user's correction.
|
|
74
104
|
If the request is `stale`, the approval context changed before execution; send a fresh `exec` request with the current command and reason instead of trying to reuse the old approval.
|
|
105
|
+
If the request is `error`, read the error text. SSH reachability, refused ports,
|
|
106
|
+
authentication failures, and host-key failures are current connection signals;
|
|
107
|
+
do not assume a listed server is live just because `list_servers` returned it.
|
|
108
|
+
|
|
109
|
+
If a response or request contains `policy_warnings`, mention the warning briefly
|
|
110
|
+
when it affects the next action. These warnings are best-effort safety rails, not
|
|
111
|
+
permission to skip human judgment.
|
|
75
112
|
|
|
76
113
|
## Running Flow
|
|
77
114
|
|
|
@@ -79,15 +116,16 @@ When `exec` or `get_request` returns `running`:
|
|
|
79
116
|
|
|
80
117
|
1. If the server permission is `always_run`, call `read_console(server_id)` before sending another long-running command to the same server.
|
|
81
118
|
2. Poll `get_request(request_id)` every 3-5 seconds.
|
|
82
|
-
3. Use `read_console(server_id)` between polls only when the token has `always_run` permission.
|
|
119
|
+
3. Use `read_console(server_id)` between polls only when the token has `always_run` permission. For a multi-server command, use `read_console(server_ids, tail)` to inspect several running consoles at once.
|
|
83
120
|
4. Do not start another long-running command on the same server until the active request reaches a terminal status, unless the user explicitly asks.
|
|
84
121
|
|
|
85
122
|
If the request appears stuck for an unusually long time and `read_console`
|
|
86
|
-
shows no useful progress, ask the operator before recovery
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
123
|
+
shows no useful progress, ask the operator before recovery unless they already
|
|
124
|
+
asked you to recover the session. When recovery is approved, call
|
|
125
|
+
`restart_console_session(server_id)`. This does not intentionally run a remote
|
|
126
|
+
shell command; it closes the gateway-owned persistent console session and the
|
|
127
|
+
next `exec` opens a fresh SSH session. Treat any canceled request as
|
|
128
|
+
inconclusive and re-run only the minimal safe inspection needed.
|
|
91
129
|
|
|
92
130
|
## Message Flow
|
|
93
131
|
|
package/dist/server.js
CHANGED
|
@@ -25,7 +25,7 @@ const apiTransferTimeoutMs = Number.parseInt(process.env.AIPERMISSION_TRANSFER_T
|
|
|
25
25
|
|
|
26
26
|
const server = new McpServer({
|
|
27
27
|
name: "aipermission",
|
|
28
|
-
version: "0.1.
|
|
28
|
+
version: "0.1.13",
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
server.tool(
|
|
@@ -39,35 +39,86 @@ server.tool(
|
|
|
39
39
|
|
|
40
40
|
server.tool(
|
|
41
41
|
"exec",
|
|
42
|
-
"Execute a shell command on
|
|
42
|
+
"Execute a shell command on one allowed server, or the same shell command across multiple allowed servers, through the local aipermission gateway. If status is approval_pending, follow assistant_hint and poll get_request. Long always_run commands return running; use read_console to continue watching.",
|
|
43
43
|
{
|
|
44
|
-
server_id: z.number().int().positive().describe("
|
|
44
|
+
server_id: z.number().int().positive().optional().describe("Single server id from list_servers. Use either server_id or server_ids, not both."),
|
|
45
|
+
server_ids: z.array(z.number().int().positive()).min(1).max(25).optional().describe("Multiple server ids from list_servers for bulk execution. Up to 25 targets. Use either server_id or server_ids, not both."),
|
|
45
46
|
command: z.string().min(1).describe("Shell command to execute."),
|
|
46
|
-
reason: z.string().optional().describe("Why this command is needed."),
|
|
47
|
+
reason: z.string().optional().describe("Why this command is needed. Required when using server_ids."),
|
|
47
48
|
},
|
|
48
|
-
async ({ server_id, command, reason }) => {
|
|
49
|
-
return jsonToolResult(() =>
|
|
50
|
-
server_id
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
async ({ server_id, server_ids, command, reason }) => {
|
|
50
|
+
return jsonToolResult(() => {
|
|
51
|
+
if (server_id && server_ids?.length) {
|
|
52
|
+
throw new Error("Use either server_id or server_ids, not both.");
|
|
53
|
+
}
|
|
54
|
+
if (!server_id && !server_ids?.length) {
|
|
55
|
+
throw new Error("server_id or server_ids is required.");
|
|
56
|
+
}
|
|
57
|
+
if (server_ids?.length) {
|
|
58
|
+
if (!String(reason || "").trim()) {
|
|
59
|
+
throw new Error("reason is required when using server_ids.");
|
|
60
|
+
}
|
|
61
|
+
return apiPost("/api/mcp/bulk-exec", {
|
|
62
|
+
server_ids,
|
|
63
|
+
command,
|
|
64
|
+
reason,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return apiPost("/api/mcp/exec", {
|
|
68
|
+
server_id,
|
|
69
|
+
command,
|
|
70
|
+
reason: reason || "",
|
|
71
|
+
});
|
|
72
|
+
});
|
|
54
73
|
}
|
|
55
74
|
);
|
|
56
75
|
|
|
57
76
|
server.tool(
|
|
58
77
|
"read_console",
|
|
59
|
-
"Read the latest persistent console transcript for
|
|
78
|
+
"Read the latest persistent console transcript for one allowed server, or for multiple allowed servers after a multi-server exec. Use this after a long-running exec returns running.",
|
|
60
79
|
{
|
|
61
|
-
server_id: z.number().int().positive().describe("
|
|
80
|
+
server_id: z.number().int().positive().optional().describe("Single server id from list_servers. Use either server_id or server_ids, not both."),
|
|
81
|
+
server_ids: z.array(z.number().int().positive()).min(1).max(25).optional().describe("Multiple server ids from list_servers. Up to 25 targets. Use either server_id or server_ids, not both."),
|
|
62
82
|
tail: z.number().int().positive().max(100000).optional().describe("Maximum transcript characters to return."),
|
|
63
83
|
},
|
|
64
|
-
async ({ server_id, tail }) => {
|
|
65
|
-
return jsonToolResult(() => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
84
|
+
async ({ server_id, server_ids, tail }) => {
|
|
85
|
+
return jsonToolResult(async () => {
|
|
86
|
+
if (server_id && server_ids?.length) {
|
|
87
|
+
throw new Error("Use either server_id or server_ids, not both.");
|
|
88
|
+
}
|
|
89
|
+
if (!server_id && !server_ids?.length) {
|
|
90
|
+
throw new Error("server_id or server_ids is required.");
|
|
91
|
+
}
|
|
92
|
+
const readOne = (id) => {
|
|
93
|
+
const params = new URLSearchParams({ server_id: String(id) });
|
|
94
|
+
if (tail) {
|
|
95
|
+
params.set("tail", String(tail));
|
|
96
|
+
}
|
|
97
|
+
return apiGet(`/api/mcp/console?${params.toString()}`);
|
|
98
|
+
};
|
|
99
|
+
if (server_ids?.length) {
|
|
100
|
+
const unique = new Set(server_ids);
|
|
101
|
+
if (unique.size !== server_ids.length) {
|
|
102
|
+
throw new Error("server_ids must not contain duplicates.");
|
|
103
|
+
}
|
|
104
|
+
const items = await Promise.all(server_ids.map(async (id) => {
|
|
105
|
+
try {
|
|
106
|
+
return await readOne(id);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return {
|
|
109
|
+
status: "error",
|
|
110
|
+
server_id: id,
|
|
111
|
+
error: error?.message || "failed to read console",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}));
|
|
115
|
+
return {
|
|
116
|
+
status: "ok",
|
|
117
|
+
items,
|
|
118
|
+
assistant_hint: "Inspect each item independently. A listed server may have no active console, blocked read permission, or an SSH/session error.",
|
|
119
|
+
};
|
|
69
120
|
}
|
|
70
|
-
return
|
|
121
|
+
return readOne(server_id);
|
|
71
122
|
});
|
|
72
123
|
}
|
|
73
124
|
);
|
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.1.
|
|
6
|
+
"version": "0.1.13",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"identifier": "@aipermission/mcp",
|
|
11
|
-
"version": "0.1.
|
|
11
|
+
"version": "0.1.13",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
}
|