@crediolabs/policy-builder-mcp 0.1.18 → 0.2.0
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 +5 -2
- package/dist/src/schemas.d.ts +59 -0
- package/dist/src/schemas.js +8 -0
- package/dist/src/server.js +3 -2
- package/dist/src/transports/http.js +33 -9
- package/package.json +4 -8
- package/src/schemas.ts +9 -0
- package/src/server.ts +9 -0
- package/src/transports/http.ts +34 -9
- package/dist-cjs/package.json +0 -3
- package/dist-cjs/src/index.d.ts +0 -7
- package/dist-cjs/src/index.js +0 -20
- package/dist-cjs/src/schemas.d.ts +0 -1113
- package/dist-cjs/src/schemas.js +0 -135
- package/dist-cjs/src/server.d.ts +0 -6
- package/dist-cjs/src/server.js +0 -42
- package/dist-cjs/src/tools/result.d.ts +0 -24
- package/dist-cjs/src/tools/result.js +0 -38
- package/dist-cjs/src/transports/http.d.ts +0 -21
- package/dist-cjs/src/transports/http.js +0 -159
- package/dist-cjs/src/transports/stdio.d.ts +0 -1
- package/dist-cjs/src/transports/stdio.js +0 -18
package/README.md
CHANGED
|
@@ -54,8 +54,11 @@ branch on them.
|
|
|
54
54
|
non-loopback host; it serves `127.0.0.1`, `::1` or `localhost` unless the
|
|
55
55
|
embedding caller explicitly opts out (`allowExternalHost: true` via the
|
|
56
56
|
programmatic API), because the surface is unauthenticated by design.
|
|
57
|
-
-
|
|
58
|
-
[architecture document](https://github.com/untangledfinance/octogate/blob/main/docs/architecture.md)
|
|
57
|
+
- See the
|
|
58
|
+
[architecture document](https://github.com/untangledfinance/octogate/blob/main/docs/architecture.md)
|
|
59
|
+
for what the on-chain interpreter does and does not enforce, and the
|
|
60
|
+
[repository README](https://github.com/untangledfinance/octogate#readme)
|
|
61
|
+
for the contracts' audit status.
|
|
59
62
|
|
|
60
63
|
## License
|
|
61
64
|
|
package/dist/src/schemas.d.ts
CHANGED
|
@@ -427,18 +427,66 @@ export declare const SynthesizePolicyToolShape: {
|
|
|
427
427
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
428
428
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
429
429
|
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
430
|
+
oraclePriceBound: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
431
|
+
asset: z.ZodEffects<z.ZodString, string, string>;
|
|
432
|
+
operator: z.ZodEnum<["eq", "lt", "lte", "gt", "gte"]>;
|
|
433
|
+
value: z.ZodString;
|
|
434
|
+
decimals: z.ZodNumber;
|
|
435
|
+
}, "strip", z.ZodTypeAny, {
|
|
436
|
+
value: string;
|
|
437
|
+
operator: "eq" | "lt" | "lte" | "gt" | "gte";
|
|
438
|
+
asset: string;
|
|
439
|
+
decimals: number;
|
|
440
|
+
}, {
|
|
441
|
+
value: string;
|
|
442
|
+
operator: "eq" | "lt" | "lte" | "gt" | "gte";
|
|
443
|
+
asset: string;
|
|
444
|
+
decimals: number;
|
|
445
|
+
}>, "many">>;
|
|
430
446
|
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
431
447
|
windowSeconds: z.ZodOptional<z.ZodNumber>;
|
|
432
448
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
433
449
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
434
450
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
435
451
|
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
452
|
+
oraclePriceBound: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
453
|
+
asset: z.ZodEffects<z.ZodString, string, string>;
|
|
454
|
+
operator: z.ZodEnum<["eq", "lt", "lte", "gt", "gte"]>;
|
|
455
|
+
value: z.ZodString;
|
|
456
|
+
decimals: z.ZodNumber;
|
|
457
|
+
}, "strip", z.ZodTypeAny, {
|
|
458
|
+
value: string;
|
|
459
|
+
operator: "eq" | "lt" | "lte" | "gt" | "gte";
|
|
460
|
+
asset: string;
|
|
461
|
+
decimals: number;
|
|
462
|
+
}, {
|
|
463
|
+
value: string;
|
|
464
|
+
operator: "eq" | "lt" | "lte" | "gt" | "gte";
|
|
465
|
+
asset: string;
|
|
466
|
+
decimals: number;
|
|
467
|
+
}>, "many">>;
|
|
436
468
|
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
437
469
|
windowSeconds: z.ZodOptional<z.ZodNumber>;
|
|
438
470
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
439
471
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
440
472
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
441
473
|
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
474
|
+
oraclePriceBound: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
475
|
+
asset: z.ZodEffects<z.ZodString, string, string>;
|
|
476
|
+
operator: z.ZodEnum<["eq", "lt", "lte", "gt", "gte"]>;
|
|
477
|
+
value: z.ZodString;
|
|
478
|
+
decimals: z.ZodNumber;
|
|
479
|
+
}, "strip", z.ZodTypeAny, {
|
|
480
|
+
value: string;
|
|
481
|
+
operator: "eq" | "lt" | "lte" | "gt" | "gte";
|
|
482
|
+
asset: string;
|
|
483
|
+
decimals: number;
|
|
484
|
+
}, {
|
|
485
|
+
value: string;
|
|
486
|
+
operator: "eq" | "lt" | "lte" | "gt" | "gte";
|
|
487
|
+
asset: string;
|
|
488
|
+
decimals: number;
|
|
489
|
+
}>, "many">>;
|
|
442
490
|
}, z.ZodTypeAny, "passthrough">>>;
|
|
443
491
|
readonly confidenceOverride: z.ZodOptional<z.ZodObject<{
|
|
444
492
|
threshold: z.ZodNumber;
|
|
@@ -1103,6 +1151,17 @@ export declare const RevokePolicyToolShape: {
|
|
|
1103
1151
|
readonly rpcUrl: z.ZodOptional<z.ZodString>;
|
|
1104
1152
|
readonly baseFee: z.ZodOptional<z.ZodNumber>;
|
|
1105
1153
|
};
|
|
1154
|
+
/** Flat ZodRawShape for `merge_policy`: the tightening remedy when two rules
|
|
1155
|
+
* our interpreter polices can serve the same calls. Two steps, in order. */
|
|
1156
|
+
export declare const MergePolicyToolShape: {
|
|
1157
|
+
readonly ruleId: z.ZodNumber;
|
|
1158
|
+
readonly incomingPredicateBlobBase64: z.ZodString;
|
|
1159
|
+
readonly step: z.ZodEnum<["detach", "reinstall"]>;
|
|
1160
|
+
readonly smartAccount: z.ZodOptional<z.ZodString>;
|
|
1161
|
+
readonly sourceAccount: z.ZodOptional<z.ZodString>;
|
|
1162
|
+
readonly rpcUrl: z.ZodOptional<z.ZodString>;
|
|
1163
|
+
readonly baseFee: z.ZodOptional<z.ZodNumber>;
|
|
1164
|
+
};
|
|
1106
1165
|
/** Flat ZodRawShape for `get_interpreter_info`. `verifyLive` triggers an
|
|
1107
1166
|
* optional RPC `grammar_version()` call so the caller can verify the
|
|
1108
1167
|
* deployed contract matches the pin. */
|
package/dist/src/schemas.js
CHANGED
|
@@ -112,6 +112,14 @@ export const RevokePolicyToolShape = {
|
|
|
112
112
|
ruleId: z.number().int().nonnegative().optional(),
|
|
113
113
|
interpreterAddress: z.string().optional(),
|
|
114
114
|
};
|
|
115
|
+
/** Flat ZodRawShape for `merge_policy`: the tightening remedy when two rules
|
|
116
|
+
* our interpreter polices can serve the same calls. Two steps, in order. */
|
|
117
|
+
export const MergePolicyToolShape = {
|
|
118
|
+
...SmartAccountToolShape,
|
|
119
|
+
ruleId: z.number().int().nonnegative(),
|
|
120
|
+
incomingPredicateBlobBase64: z.string().min(1),
|
|
121
|
+
step: z.enum(['detach', 'reinstall']),
|
|
122
|
+
};
|
|
115
123
|
/** Flat ZodRawShape for `get_interpreter_info`. `verifyLive` triggers an
|
|
116
124
|
* optional RPC `grammar_version()` call so the caller can verify the
|
|
117
125
|
* deployed contract matches the pin. */
|
package/dist/src/server.js
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
// Stateless: a fresh McpServer is constructed per transport (stdio/HTTP). No
|
|
11
11
|
// shared mutable state across calls; nothing here caches, queues, or holds
|
|
12
12
|
// key material.
|
|
13
|
-
import { runGetInterpreterInfo, runInstallPolicy, runRecordTransaction, runRevokePolicy, runSimulatePolicy, runSynthesizePolicy, runVerifyPolicy, } from '@crediolabs/policy-synth/run';
|
|
13
|
+
import { runGetInterpreterInfo, runInstallPolicy, runMergePolicy, runRecordTransaction, runRevokePolicy, runSimulatePolicy, runSynthesizePolicy, runVerifyPolicy, } from '@crediolabs/policy-synth/run';
|
|
14
14
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
15
|
-
import { GetInterpreterInfoToolShape, InstallPolicyToolShape, RecordTransactionToolShape, RevokePolicyToolShape, SimulatePolicyToolShape, SynthesizePolicyToolShape, VerifyPolicyToolShape, } from "./schemas.js";
|
|
15
|
+
import { GetInterpreterInfoToolShape, InstallPolicyToolShape, MergePolicyToolShape, RecordTransactionToolShape, RevokePolicyToolShape, SimulatePolicyToolShape, SynthesizePolicyToolShape, VerifyPolicyToolShape, } from "./schemas.js";
|
|
16
16
|
import { mcpResultFromCore } from "./tools/result.js";
|
|
17
17
|
/** Our envelope types `structuredContent` precisely (T / ToolError); the SDK's
|
|
18
18
|
* CallToolResult widens it to Record<string, unknown>, so the nominal types
|
|
@@ -34,5 +34,6 @@ export function registerTools(server) {
|
|
|
34
34
|
server.tool('verify_policy', 'Run the static minimality check on a proposed PredicateNode against a RecordedTransaction (no conjunct is load-bearing-free). Returns VERIFICATION_FAILED with the dropped-constraint fingerprints when the predicate is over-broad.', VerifyPolicyToolShape, (args) => runVerifyPolicy(args).then(toCallToolResult));
|
|
35
35
|
server.tool('install_policy', 'Build an UNSIGNED Soroban transaction XDR for `account.add_context_rule(...)` that installs a new policy rule on the given smart account. The wallet signs the returned XDR - the signature IS the user-confirmation step (this server is stateless and holds no key material, so there is no two-call confirm pair). Only CALL 1 is emitted; the interpreter `install` follow-up needs the rule id the account assigns in call 1 and is documented in `followUp` in the response.', InstallPolicyToolShape, (args) => runInstallPolicy(args).then(toCallToolResult));
|
|
36
36
|
server.tool('revoke_policy', 'Build an UNSIGNED Soroban transaction XDR for `account.remove_context_rule(ruleId)` that removes a policy rule from the given smart account. The smart account handles uninstalling each attached policy itself. Auth is master-only - the source account MUST be the master signer set; delegated signers cannot uninstall.', RevokePolicyToolShape, (args) => runRevokePolicy(args).then(toCallToolResult));
|
|
37
|
+
server.tool('merge_policy', "Tighten a rule by replacing its predicate with the conjunction of the installed one and a new one. This is the remedy for the overlap `install_policy` reports between two rules the interpreter polices: because OpenZeppelin enforces only the rule a signer names, adding a second, tighter rule restricts nothing, so the restriction has to become one predicate. TWO transactions, in order: call with step 'detach' to remove the current attachment, wait for it to confirm, then call with step 'reinstall'. Detaching uninstalls the policy, which RESETS every counter on the rule, and the rule is unpoliced in between - both are reported in `warnings`.", MergePolicyToolShape, (args) => runMergePolicy(args).then(toCallToolResult));
|
|
37
38
|
server.tool('get_interpreter_info', 'Read-only fingerprint lookup for the policy interpreter contract: returns the pinned address, grammar version, and wasm sha256 (from the pinned constants + SELF_VERSION). When `verifyLive=true`, performs an additional `grammar_version()` RPC call against the pinned address and reports whether the deployed contract matches the pin - a live mismatch check is more useful than a fabricated audit field.', GetInterpreterInfoToolShape, (args) => runGetInterpreterInfo(args).then(toCallToolResult));
|
|
38
39
|
}
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Streamable HTTP transport (hosted). Uses the Node http module directly so
|
|
4
4
|
// the package stays thin - no express / hono dep. We run in STATELESS mode
|
|
5
|
-
// (sessionIdGenerator
|
|
6
|
-
//
|
|
5
|
+
// (no `sessionIdGenerator` option), and the SDK 1.26+ requires a FRESH
|
|
6
|
+
// transport per request in stateless mode: reusing one across requests lets
|
|
7
|
+
// concurrent clients share internal message-id state, which is exactly the
|
|
8
|
+
// cross-client data leak GHSA-345p-7cg4-v4c7 warns about. We therefore build
|
|
9
|
+
// a transport inside the request handler, connect it, dispatch the request,
|
|
10
|
+
// then close it (close() resets the McpServer's transport slot so the next
|
|
11
|
+
// request can reconnect cleanly).
|
|
7
12
|
//
|
|
8
13
|
// Single endpoint: POST /mcp (the SDK also accepts GET for SSE streaming, but
|
|
9
14
|
// the T1 surface does not emit server-initiated messages so we omit it).
|
|
@@ -34,13 +39,11 @@ export async function startHttpServer(opts) {
|
|
|
34
39
|
if (!LOOPBACK_HOSTS.has(host) && opts.allowExternalHost !== true) {
|
|
35
40
|
throw new Error(`startHttpServer: refusing to bind host ${host}: the MCP surface is unauthenticated, so only loopback (127.0.0.1, ::1, localhost) is permitted by default. Pass \`allowExternalHost: true\` to opt in to a non-loopback bind.`);
|
|
36
41
|
}
|
|
42
|
+
// The McpServer is stateless and per-process; we keep ONE instance alive
|
|
43
|
+
// for the lifetime of the listener, but each request creates + connects a
|
|
44
|
+
// fresh StreamableHTTPServerTransport (SDK 1.26+ refuses to reuse a
|
|
45
|
+
// stateless transport across requests).
|
|
37
46
|
const server = createMcpServer();
|
|
38
|
-
// One transport per server (the SDK reuses the transport for every request
|
|
39
|
-
// in stateless mode). We connect it once at startup and reuse it.
|
|
40
|
-
const transport = new StreamableHTTPServerTransport({
|
|
41
|
-
sessionIdGenerator: undefined,
|
|
42
|
-
});
|
|
43
|
-
await server.connect(transport);
|
|
44
47
|
const httpServer = createServer(async (req, res) => {
|
|
45
48
|
if (!req.url) {
|
|
46
49
|
sendJson(res, 400, { error: 'missing url' });
|
|
@@ -86,6 +89,20 @@ export async function startHttpServer(opts) {
|
|
|
86
89
|
});
|
|
87
90
|
return;
|
|
88
91
|
}
|
|
92
|
+
// One transport per request. Omitting `sessionIdGenerator` selects
|
|
93
|
+
// stateless mode in SDK 1.26+ (the previous `sessionIdGenerator:
|
|
94
|
+
// undefined` form is rejected because the option type no longer
|
|
95
|
+
// includes `undefined` under `exactOptionalPropertyTypes`).
|
|
96
|
+
//
|
|
97
|
+
// The cast `as unknown as Transport` is narrowly scoped to the SDK
|
|
98
|
+
// boundary: the Node wrapper exposes `onclose` via getter/setter with
|
|
99
|
+
// type `(() => void) | undefined`, while the `Transport` interface
|
|
100
|
+
// declares `onclose?: () => void`. Under `exactOptionalPropertyTypes:
|
|
101
|
+
// true` those are not structurally assignable. The runtime contract is
|
|
102
|
+
// the same (the setter accepts `() => void`), so we cast once here.
|
|
103
|
+
const transport = new StreamableHTTPServerTransport();
|
|
104
|
+
const sdkTransport = transport;
|
|
105
|
+
await server.connect(sdkTransport);
|
|
89
106
|
try {
|
|
90
107
|
// `handleRequest` writes the response and returns once the message has
|
|
91
108
|
// been dispatched. No shared state across calls in stateless mode.
|
|
@@ -107,6 +124,14 @@ export async function startHttpServer(opts) {
|
|
|
107
124
|
sendJson(res, 500, { error });
|
|
108
125
|
}
|
|
109
126
|
}
|
|
127
|
+
finally {
|
|
128
|
+
// Closing the transport fires its `onclose`, which resets
|
|
129
|
+
// `server._transport` to undefined so the next request can connect
|
|
130
|
+
// a fresh transport. We swallow the close error: a request that
|
|
131
|
+
// already wrote its response does not care whether the close path
|
|
132
|
+
// threw.
|
|
133
|
+
await transport.close().catch(() => { });
|
|
134
|
+
}
|
|
110
135
|
});
|
|
111
136
|
await new Promise((resolve, reject) => {
|
|
112
137
|
httpServer.once('error', reject);
|
|
@@ -121,7 +146,6 @@ export async function startHttpServer(opts) {
|
|
|
121
146
|
path,
|
|
122
147
|
close: async () => {
|
|
123
148
|
await new Promise((resolve) => httpServer.close(() => resolve()));
|
|
124
|
-
await transport.close().catch(() => { });
|
|
125
149
|
await server.close().catch(() => { });
|
|
126
150
|
},
|
|
127
151
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crediolabs/policy-builder-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "MCP server exposing the OZ policy-synth core (record_transaction + synthesize_policy) over stdio and Streamable HTTP transports.",
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
"types": "./dist/src/index.d.ts",
|
|
38
38
|
"bun": "./src/index.ts",
|
|
39
39
|
"import": "./dist/src/index.js",
|
|
40
|
-
"require": "./dist-cjs/src/index.js",
|
|
41
40
|
"default": "./dist/src/index.js"
|
|
42
41
|
},
|
|
43
42
|
"./package.json": "./package.json"
|
|
@@ -47,7 +46,6 @@
|
|
|
47
46
|
},
|
|
48
47
|
"files": [
|
|
49
48
|
"dist",
|
|
50
|
-
"dist-cjs",
|
|
51
49
|
"src",
|
|
52
50
|
"!src/**/*.test.ts"
|
|
53
51
|
],
|
|
@@ -56,15 +54,13 @@
|
|
|
56
54
|
},
|
|
57
55
|
"scripts": {
|
|
58
56
|
"test": "bun test",
|
|
59
|
-
"build": "tsc -p tsconfig.build.json
|
|
60
|
-
"build:esm": "tsc -p tsconfig.build.json",
|
|
61
|
-
"build:cjs": "tsc -p tsconfig.build.cjs.json && node scripts/write-cjs-package-json.cjs",
|
|
57
|
+
"build": "tsc -p tsconfig.build.json",
|
|
62
58
|
"prepublishOnly": "bun run build && bun test",
|
|
63
59
|
"prepack": "bun run build"
|
|
64
60
|
},
|
|
65
61
|
"dependencies": {
|
|
66
|
-
"@crediolabs/policy-synth": "0.
|
|
67
|
-
"@modelcontextprotocol/sdk": "1.
|
|
62
|
+
"@crediolabs/policy-synth": "0.2.0",
|
|
63
|
+
"@modelcontextprotocol/sdk": "1.26.0",
|
|
68
64
|
"@stellar/stellar-sdk": "14.4.0",
|
|
69
65
|
"zod": "3.25.76"
|
|
70
66
|
},
|
package/src/schemas.ts
CHANGED
|
@@ -167,6 +167,15 @@ export const RevokePolicyToolShape = {
|
|
|
167
167
|
interpreterAddress: z.string().optional(),
|
|
168
168
|
} as const
|
|
169
169
|
|
|
170
|
+
/** Flat ZodRawShape for `merge_policy`: the tightening remedy when two rules
|
|
171
|
+
* our interpreter polices can serve the same calls. Two steps, in order. */
|
|
172
|
+
export const MergePolicyToolShape = {
|
|
173
|
+
...SmartAccountToolShape,
|
|
174
|
+
ruleId: z.number().int().nonnegative(),
|
|
175
|
+
incomingPredicateBlobBase64: z.string().min(1),
|
|
176
|
+
step: z.enum(['detach', 'reinstall']),
|
|
177
|
+
} as const
|
|
178
|
+
|
|
170
179
|
/** Flat ZodRawShape for `get_interpreter_info`. `verifyLive` triggers an
|
|
171
180
|
* optional RPC `grammar_version()` call so the caller can verify the
|
|
172
181
|
* deployed contract matches the pin. */
|
package/src/server.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type { ToolResponse } from '@crediolabs/policy-synth'
|
|
|
15
15
|
import {
|
|
16
16
|
runGetInterpreterInfo,
|
|
17
17
|
runInstallPolicy,
|
|
18
|
+
runMergePolicy,
|
|
18
19
|
runRecordTransaction,
|
|
19
20
|
runRevokePolicy,
|
|
20
21
|
runSimulatePolicy,
|
|
@@ -26,6 +27,7 @@ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
|
|
|
26
27
|
import {
|
|
27
28
|
GetInterpreterInfoToolShape,
|
|
28
29
|
InstallPolicyToolShape,
|
|
30
|
+
MergePolicyToolShape,
|
|
29
31
|
RecordTransactionToolShape,
|
|
30
32
|
RevokePolicyToolShape,
|
|
31
33
|
SimulatePolicyToolShape,
|
|
@@ -97,6 +99,13 @@ export function registerTools(server: McpServer): void {
|
|
|
97
99
|
(args) => runRevokePolicy(args).then(toCallToolResult)
|
|
98
100
|
)
|
|
99
101
|
|
|
102
|
+
server.tool(
|
|
103
|
+
'merge_policy',
|
|
104
|
+
"Tighten a rule by replacing its predicate with the conjunction of the installed one and a new one. This is the remedy for the overlap `install_policy` reports between two rules the interpreter polices: because OpenZeppelin enforces only the rule a signer names, adding a second, tighter rule restricts nothing, so the restriction has to become one predicate. TWO transactions, in order: call with step 'detach' to remove the current attachment, wait for it to confirm, then call with step 'reinstall'. Detaching uninstalls the policy, which RESETS every counter on the rule, and the rule is unpoliced in between - both are reported in `warnings`.",
|
|
105
|
+
MergePolicyToolShape,
|
|
106
|
+
(args) => runMergePolicy(args).then(toCallToolResult)
|
|
107
|
+
)
|
|
108
|
+
|
|
100
109
|
server.tool(
|
|
101
110
|
'get_interpreter_info',
|
|
102
111
|
'Read-only fingerprint lookup for the policy interpreter contract: returns the pinned address, grammar version, and wasm sha256 (from the pinned constants + SELF_VERSION). When `verifyLive=true`, performs an additional `grammar_version()` RPC call against the pinned address and reports whether the deployed contract matches the pin - a live mismatch check is more useful than a fabricated audit field.',
|
package/src/transports/http.ts
CHANGED
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Streamable HTTP transport (hosted). Uses the Node http module directly so
|
|
4
4
|
// the package stays thin - no express / hono dep. We run in STATELESS mode
|
|
5
|
-
// (sessionIdGenerator
|
|
6
|
-
//
|
|
5
|
+
// (no `sessionIdGenerator` option), and the SDK 1.26+ requires a FRESH
|
|
6
|
+
// transport per request in stateless mode: reusing one across requests lets
|
|
7
|
+
// concurrent clients share internal message-id state, which is exactly the
|
|
8
|
+
// cross-client data leak GHSA-345p-7cg4-v4c7 warns about. We therefore build
|
|
9
|
+
// a transport inside the request handler, connect it, dispatch the request,
|
|
10
|
+
// then close it (close() resets the McpServer's transport slot so the next
|
|
11
|
+
// request can reconnect cleanly).
|
|
7
12
|
//
|
|
8
13
|
// Single endpoint: POST /mcp (the SDK also accepts GET for SSE streaming, but
|
|
9
14
|
// the T1 surface does not emit server-initiated messages so we omit it).
|
|
@@ -22,6 +27,7 @@
|
|
|
22
27
|
import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'
|
|
23
28
|
import type { ToolError } from '@crediolabs/policy-synth'
|
|
24
29
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
|
|
30
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
|
|
25
31
|
import { createMcpServer } from '../server.ts'
|
|
26
32
|
|
|
27
33
|
const LOOPBACK_HOSTS = new Set(['127.0.0.1', '::1', 'localhost'])
|
|
@@ -60,13 +66,11 @@ export async function startHttpServer(opts: StartHttpServerOptions): Promise<Run
|
|
|
60
66
|
`startHttpServer: refusing to bind host ${host}: the MCP surface is unauthenticated, so only loopback (127.0.0.1, ::1, localhost) is permitted by default. Pass \`allowExternalHost: true\` to opt in to a non-loopback bind.`
|
|
61
67
|
)
|
|
62
68
|
}
|
|
69
|
+
// The McpServer is stateless and per-process; we keep ONE instance alive
|
|
70
|
+
// for the lifetime of the listener, but each request creates + connects a
|
|
71
|
+
// fresh StreamableHTTPServerTransport (SDK 1.26+ refuses to reuse a
|
|
72
|
+
// stateless transport across requests).
|
|
63
73
|
const server = createMcpServer()
|
|
64
|
-
// One transport per server (the SDK reuses the transport for every request
|
|
65
|
-
// in stateless mode). We connect it once at startup and reuse it.
|
|
66
|
-
const transport = new StreamableHTTPServerTransport({
|
|
67
|
-
sessionIdGenerator: undefined,
|
|
68
|
-
})
|
|
69
|
-
await server.connect(transport)
|
|
70
74
|
|
|
71
75
|
const httpServer: Server = createServer(async (req, res) => {
|
|
72
76
|
if (!req.url) {
|
|
@@ -114,6 +118,21 @@ export async function startHttpServer(opts: StartHttpServerOptions): Promise<Run
|
|
|
114
118
|
return
|
|
115
119
|
}
|
|
116
120
|
|
|
121
|
+
// One transport per request. Omitting `sessionIdGenerator` selects
|
|
122
|
+
// stateless mode in SDK 1.26+ (the previous `sessionIdGenerator:
|
|
123
|
+
// undefined` form is rejected because the option type no longer
|
|
124
|
+
// includes `undefined` under `exactOptionalPropertyTypes`).
|
|
125
|
+
//
|
|
126
|
+
// The cast `as unknown as Transport` is narrowly scoped to the SDK
|
|
127
|
+
// boundary: the Node wrapper exposes `onclose` via getter/setter with
|
|
128
|
+
// type `(() => void) | undefined`, while the `Transport` interface
|
|
129
|
+
// declares `onclose?: () => void`. Under `exactOptionalPropertyTypes:
|
|
130
|
+
// true` those are not structurally assignable. The runtime contract is
|
|
131
|
+
// the same (the setter accepts `() => void`), so we cast once here.
|
|
132
|
+
const transport = new StreamableHTTPServerTransport()
|
|
133
|
+
const sdkTransport = transport as unknown as Transport
|
|
134
|
+
await server.connect(sdkTransport)
|
|
135
|
+
|
|
117
136
|
try {
|
|
118
137
|
// `handleRequest` writes the response and returns once the message has
|
|
119
138
|
// been dispatched. No shared state across calls in stateless mode.
|
|
@@ -132,6 +151,13 @@ export async function startHttpServer(opts: StartHttpServerOptions): Promise<Run
|
|
|
132
151
|
}
|
|
133
152
|
sendJson(res, 500, { error })
|
|
134
153
|
}
|
|
154
|
+
} finally {
|
|
155
|
+
// Closing the transport fires its `onclose`, which resets
|
|
156
|
+
// `server._transport` to undefined so the next request can connect
|
|
157
|
+
// a fresh transport. We swallow the close error: a request that
|
|
158
|
+
// already wrote its response does not care whether the close path
|
|
159
|
+
// threw.
|
|
160
|
+
await transport.close().catch(() => {})
|
|
135
161
|
}
|
|
136
162
|
})
|
|
137
163
|
|
|
@@ -149,7 +175,6 @@ export async function startHttpServer(opts: StartHttpServerOptions): Promise<Run
|
|
|
149
175
|
path,
|
|
150
176
|
close: async () => {
|
|
151
177
|
await new Promise<void>((resolve) => httpServer.close(() => resolve()))
|
|
152
|
-
await transport.close().catch(() => {})
|
|
153
178
|
await server.close().catch(() => {})
|
|
154
179
|
},
|
|
155
180
|
}
|
package/dist-cjs/package.json
DELETED
package/dist-cjs/src/index.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { type RunRecordTransactionInput, type RunSynthesizePolicyInput, runRecordTransaction, runSynthesizePolicy, } from '@crediolabs/policy-synth/run';
|
|
2
|
-
export { RecordTransactionToolShape, SynthesizePolicyToolShape, } from './schemas.ts';
|
|
3
|
-
export { createMcpServer, registerTools } from './server.ts';
|
|
4
|
-
export type { McpToolError, McpToolResult } from './tools/result.ts';
|
|
5
|
-
export { mcpErrorFromCore, mcpResultFromCore } from './tools/result.ts';
|
|
6
|
-
export { startHttpServer } from './transports/http.ts';
|
|
7
|
-
export { startStdioServer } from './transports/stdio.ts';
|
package/dist-cjs/src/index.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// packages/policy-builder-mcp/src/index.ts - public re-exports for the MCP server package.
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.startStdioServer = exports.startHttpServer = exports.mcpResultFromCore = exports.mcpErrorFromCore = exports.registerTools = exports.createMcpServer = exports.SynthesizePolicyToolShape = exports.RecordTransactionToolShape = exports.runSynthesizePolicy = exports.runRecordTransaction = void 0;
|
|
5
|
-
var run_1 = require("@crediolabs/policy-synth/run");
|
|
6
|
-
Object.defineProperty(exports, "runRecordTransaction", { enumerable: true, get: function () { return run_1.runRecordTransaction; } });
|
|
7
|
-
Object.defineProperty(exports, "runSynthesizePolicy", { enumerable: true, get: function () { return run_1.runSynthesizePolicy; } });
|
|
8
|
-
var schemas_ts_1 = require("./schemas.js");
|
|
9
|
-
Object.defineProperty(exports, "RecordTransactionToolShape", { enumerable: true, get: function () { return schemas_ts_1.RecordTransactionToolShape; } });
|
|
10
|
-
Object.defineProperty(exports, "SynthesizePolicyToolShape", { enumerable: true, get: function () { return schemas_ts_1.SynthesizePolicyToolShape; } });
|
|
11
|
-
var server_ts_1 = require("./server.js");
|
|
12
|
-
Object.defineProperty(exports, "createMcpServer", { enumerable: true, get: function () { return server_ts_1.createMcpServer; } });
|
|
13
|
-
Object.defineProperty(exports, "registerTools", { enumerable: true, get: function () { return server_ts_1.registerTools; } });
|
|
14
|
-
var result_ts_1 = require("./tools/result.js");
|
|
15
|
-
Object.defineProperty(exports, "mcpErrorFromCore", { enumerable: true, get: function () { return result_ts_1.mcpErrorFromCore; } });
|
|
16
|
-
Object.defineProperty(exports, "mcpResultFromCore", { enumerable: true, get: function () { return result_ts_1.mcpResultFromCore; } });
|
|
17
|
-
var http_ts_1 = require("./transports/http.js");
|
|
18
|
-
Object.defineProperty(exports, "startHttpServer", { enumerable: true, get: function () { return http_ts_1.startHttpServer; } });
|
|
19
|
-
var stdio_ts_1 = require("./transports/stdio.js");
|
|
20
|
-
Object.defineProperty(exports, "startStdioServer", { enumerable: true, get: function () { return stdio_ts_1.startStdioServer; } });
|