@aipermission/mcp 0.2.54 → 0.2.55

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 CHANGED
@@ -86,7 +86,7 @@ does this automatically.
86
86
  ## Tools
87
87
 
88
88
  `AIPERMISSION_HTTP_TIMEOUT_MS` sets the gateway request deadline (default:
89
- 60000 milliseconds; maximum: 600000). The value must be a positive base-10
89
+ 95000 milliseconds; maximum: 600000). The value must be a positive base-10
90
90
  integer; invalid configuration stops the bridge instead of silently changing
91
91
  the deadline. It covers both response headers and the complete response body,
92
92
  including streamed bodies. A timeout does not prove that a submitted
@@ -228,8 +228,11 @@ disclose secrets. Never automatically retry `submission_unknown`.
228
228
 
229
229
  Connector responses can include `approval_pending` or `running`. Poll
230
230
  `get_connector_action_request(request_id)` until the request reaches a terminal
231
- status. `outcome_unknown` is terminal and means the gateway could not prove the
232
- remote outcome after interruption; inspect target state or ask the operator
231
+ status. Failed action calls carry the MCP `isError` flag; reading a recorded
232
+ failed request is a successful read and does not. A `stopped` call response is a
233
+ gateway control result, not a recorded request. `outcome_unknown` is terminal
234
+ and means the gateway could not prove the remote outcome after interruption;
235
+ inspect target state or ask the operator
233
236
  before retrying. Gateway API errors with that status retain their request id,
234
237
  assistant hint, and bounded retry delay in the MCP error envelope. MCP tool
235
238
  responses never include file contents, gateway
package/dist/config.js CHANGED
@@ -1,4 +1,4 @@
1
- const defaultHTTPTimeoutMs = 60_000;
1
+ const defaultHTTPTimeoutMs = 95_000;
2
2
  const maxHTTPTimeoutMs = 10 * 60_000;
3
3
 
4
4
  export function parseHTTPTimeout(value) {
@@ -0,0 +1,27 @@
1
+ // Code generated by backend/cmd/openapi. DO NOT EDIT.
2
+
3
+ export const connectorActionStatuses = Object.freeze([
4
+ "completed",
5
+ "failed",
6
+ "canceled",
7
+ "running",
8
+ "approval_pending",
9
+ "blocked",
10
+ "stale",
11
+ "declined",
12
+ "error",
13
+ "outcome_unknown",
14
+ ]);
15
+
16
+ export const connectorRetryClasses = Object.freeze(["read_only", "idempotent", "conditional", "non_idempotent"]);
17
+
18
+ export const executionRules = Object.freeze(["always_run", "approval_required", "blocked"]);
19
+
20
+ export const connectorActionResponseRequiredFields = Object.freeze([
21
+ "status",
22
+ "request_id",
23
+ "target_ref",
24
+ "connector_kind",
25
+ "action_name",
26
+ "retry_policy",
27
+ ]);
package/dist/init.js CHANGED
@@ -609,29 +609,40 @@ function removeTOMLServer(source, name) {
609
609
  }
610
610
 
611
611
  function scanTOMLHeader(line, state) {
612
- if (state.multiline) {
613
- if (hasMultilineDelimiter(line, state.multiline)) state.multiline = "";
614
- return null;
615
- }
616
612
  const trimmed = line.trimStart();
617
- if (trimmed.startsWith("[")) {
613
+ if (!state.multiline && trimmed.startsWith("[")) {
618
614
  try {
619
615
  return findMarkerPath(parseTOML(`${line}\n__aipermission_header_marker = true\n`));
620
616
  } catch {
621
- return null;
617
+ // A string can start with a bracket; scan it before the next line.
622
618
  }
623
619
  }
624
- for (const delimiter of ['"""', "'''"]) {
625
- if (!hasMultilineDelimiter(line, delimiter)) continue;
626
- if ((line.split(delimiter).length - 1) % 2 === 1) state.multiline = delimiter;
627
- break;
628
- }
620
+ scanTOMLStrings(line, state);
629
621
  return null;
630
622
  }
631
623
 
632
- function hasMultilineDelimiter(line, delimiter) {
633
- const comment = line.indexOf("#");
634
- return (comment < 0 ? line : line.slice(0, comment)).includes(delimiter);
624
+ function scanTOMLStrings(line, state) {
625
+ let quote = state.multiline;
626
+ for (let index = 0; index < line.length; index += 1) {
627
+ const character = line[index];
628
+ if (!quote && character === "#") break;
629
+ if (quote.startsWith('"') && character === "\\") {
630
+ index += 1;
631
+ continue;
632
+ }
633
+ if (quote) {
634
+ if (line.startsWith(quote, index)) {
635
+ index += quote.length - 1;
636
+ quote = "";
637
+ }
638
+ continue;
639
+ }
640
+ if (character !== '"' && character !== "'") continue;
641
+ const triple = character.repeat(3);
642
+ quote = line.startsWith(triple, index) ? triple : character;
643
+ index += quote.length - 1;
644
+ }
645
+ state.multiline = quote.length === 3 ? quote : "";
635
646
  }
636
647
 
637
648
  function findMarkerPath(value, pathParts = []) {
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { connectorActionStatuses, connectorRetryClasses } from "./generated-connector-contract.js";
2
3
 
3
4
  const positiveID = z.number().int().positive();
4
5
  const nonNegativeInteger = z.number().int().nonnegative();
@@ -97,7 +98,7 @@ const outputHintSchema = z
97
98
  .strict();
98
99
  const retryPolicySchema = z
99
100
  .object({
100
- class: z.string(),
101
+ class: z.enum([...connectorRetryClasses]),
101
102
  precondition_fields: z.array(z.string()).optional(),
102
103
  guidance: z.string(),
103
104
  })
@@ -119,28 +120,39 @@ const actionDefinitionSchema = z
119
120
 
120
121
  const connectorActionsSchema = z.object({ items: z.array(actionDefinitionSchema) }).strict();
121
122
 
122
- const connectorActionResponseSchema = z
123
+ const connectorActionRequestSchema = z
123
124
  .object({
124
- status: z.string(),
125
- request_id: positiveID.optional(),
126
- target_ref: z.string().optional(),
125
+ status: z.enum([...connectorActionStatuses]),
126
+ request_id: positiveID,
127
+ target_ref: z.string(),
127
128
  target_name: z.string().optional(),
128
- connector_kind: z.string().optional(),
129
+ connector_kind: z.string(),
129
130
  profile_label: z.string().optional(),
130
- action_name: z.string().optional(),
131
+ action_name: z.string(),
131
132
  input: z.record(z.unknown()).optional(),
132
133
  // Connector-owned output is intentionally opaque. The gateway credential
133
134
  // boundary redacts values; this schema owns only the shared MCP envelope.
134
135
  output: z.unknown().optional(),
135
136
  display_text: z.string().optional(),
136
137
  error: z.string().optional(),
137
- retry_policy: retryPolicySchema.optional(),
138
- retry_after_seconds: nonNegativeInteger.optional(),
138
+ retry_policy: retryPolicySchema,
139
+ retry_after_seconds: nonNegativeInteger.max(3600).optional(),
139
140
  assistant_hint: z.string().optional(),
140
141
  output_withheld: z.boolean().optional(),
141
142
  replayed: z.boolean().optional(),
142
143
  })
143
- .strict();
144
+ .strict()
145
+ .superRefine((value, context) => {
146
+ if (value.output_withheld) {
147
+ if (value.input !== undefined || value.output !== undefined || value.display_text !== undefined || value.error !== undefined) {
148
+ context.addIssue({ code: z.ZodIssueCode.custom, message: "withheld action output must not contain request or result content" });
149
+ }
150
+ } else if (!value.target_ref || !value.connector_kind || !value.action_name) {
151
+ context.addIssue({ code: z.ZodIssueCode.custom, message: "action response identity is required" });
152
+ }
153
+ });
154
+ const connectorActionStoppedSchema = z.object({ status: z.literal("stopped"), error: z.string().min(1) }).strict();
155
+ const connectorActionCallSchema = z.union([connectorActionRequestSchema, connectorActionStoppedSchema]);
144
156
 
145
157
  const vaultItemSchema = z
146
158
  .object({
@@ -248,7 +260,8 @@ export const responseContracts = Object.freeze({
248
260
  connectorTargets: z.array(connectorTargetSchema),
249
261
  connectorHelp: connectorHelpSchema,
250
262
  connectorActions: connectorActionsSchema,
251
- connectorAction: connectorActionResponseSchema,
263
+ connectorActionCall: connectorActionCallSchema,
264
+ connectorActionRequest: connectorActionRequestSchema,
252
265
  vaultItems: vaultItemsSchema,
253
266
  vaultAction: vaultActionResponseSchema,
254
267
  });
package/dist/results.js CHANGED
@@ -45,7 +45,7 @@ export function errorResult(error) {
45
45
  };
46
46
  }
47
47
 
48
- export async function jsonToolResult(callback, project, mutationContext = null) {
48
+ export async function jsonToolResult(callback, project, mutationContext = null, isFailure = () => false) {
49
49
  if (typeof project !== "function") {
50
50
  return errorResult(new Error("MCP tool result projector is required."));
51
51
  }
@@ -56,12 +56,23 @@ export async function jsonToolResult(callback, project, mutationContext = null)
56
56
  return errorResult(error);
57
57
  }
58
58
  try {
59
- return textResult(project(value));
59
+ const projected = project(value);
60
+ const result = textResult(projected);
61
+ return isFailure(projected) ? { ...result, isError: true } : result;
60
62
  } catch (error) {
61
63
  return errorResult(mutationContext ? projectionOutcomeUnknown(error, mutationContext) : error);
62
64
  }
63
65
  }
64
66
 
67
+ export function jsonActionToolResult(callback, project, mutationContext) {
68
+ return jsonToolResult(
69
+ callback,
70
+ project,
71
+ mutationContext,
72
+ (value) => !["completed", "approval_pending", "running"].includes(value.status),
73
+ );
74
+ }
75
+
65
76
  function projectionOutcomeUnknown(cause, context) {
66
77
  const error = new Error(
67
78
  "The gateway accepted the mutation, but its response failed MCP contract validation. The operation may have completed.",
package/dist/server.js CHANGED
@@ -17,7 +17,7 @@ import { parseHTTPTimeout } from "./config.js";
17
17
  import { idempotencyKeySchema } from "./idempotency-key.js";
18
18
  import { normalizeLocalAPIURL } from "./local-url.js";
19
19
  import { projectGatewaySuccess, responseContracts } from "./response-contracts.js";
20
- import { jsonToolResult } from "./results.js";
20
+ import { jsonActionToolResult, jsonToolResult } from "./results.js";
21
21
  import { externalActionAnnotations, localMutationAnnotations, localReadAnnotations } from "./tool-annotations.js";
22
22
 
23
23
  const packageMetadata = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
@@ -100,7 +100,7 @@ server.tool(
100
100
  },
101
101
  externalActionAnnotations,
102
102
  async ({ target_ref, action_name, input, reason, idempotency_key }, { signal }) => {
103
- return jsonToolResult(
103
+ return jsonActionToolResult(
104
104
  () =>
105
105
  apiPost(
106
106
  "/api/mcp/connector-actions/call",
@@ -113,7 +113,7 @@ server.tool(
113
113
  },
114
114
  { signal },
115
115
  ),
116
- (value) => projectGatewaySuccess(responseContracts.connectorAction, value),
116
+ (value) => projectGatewaySuccess(responseContracts.connectorActionCall, value),
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.connectorAction, value),
132
+ (value) => projectGatewaySuccess(responseContracts.connectorActionRequest, value),
133
133
  );
134
134
  },
135
135
  );
@@ -158,7 +158,7 @@ server.tool(
158
158
  callVaultActionSchema,
159
159
  externalActionAnnotations,
160
160
  async ({ project_ref, action_name, input, reason, idempotency_key }, { signal }) => {
161
- return jsonToolResult(
161
+ return jsonActionToolResult(
162
162
  () =>
163
163
  apiPost(
164
164
  "/api/mcp/vault-actions/call",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipermission/mcp",
3
- "version": "0.2.54",
3
+ "version": "0.2.55",
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",
@@ -51,7 +51,7 @@
51
51
  "dependencies": {
52
52
  "@modelcontextprotocol/sdk": "1.30.0",
53
53
  "smol-toml": "1.8.0",
54
- "yaml": "2.9.0",
54
+ "yaml": "2.9.1",
55
55
  "zod": "3.25.76"
56
56
  },
57
57
  "devDependencies": {
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.54",
6
+ "version": "0.2.55",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "identifier": "@aipermission/mcp",
11
- "version": "0.2.54",
11
+ "version": "0.2.55",
12
12
  "transport": {
13
13
  "type": "stdio"
14
14
  }