@crediolabs/policy-builder-mcp 0.1.12 → 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.
@@ -22,17 +22,24 @@
22
22
  // the strict schemas - a drift in field type or optionality breaks the
23
23
  // SDK's emitted JSON Schema.
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.SynthesizePolicyToolShape = exports.RecordTransactionToolShape = exports.ToolErrorSchema = exports.SynthesizePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.OzAdapterConfigSchema = exports.NetworkSchema = exports.MandateSpecSchema = exports.InterpreterOptionsSchema = exports.ComposeUserResponsesSchema = void 0;
25
+ exports.GetInterpreterInfoToolShape = exports.RevokePolicyToolShape = exports.InstallPolicyToolShape = exports.VerifyPolicyToolShape = exports.SimulatePolicyToolShape = exports.SynthesizePolicyToolShape = exports.RecordTransactionToolShape = exports.VerifyPolicyInputSchema = exports.ToolErrorSchema = exports.SynthesizePolicyInputSchema = exports.SimulatePolicyInputSchema = exports.RevokePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.PredicateNodeSchema = exports.OzAdapterConfigSchema = exports.OraclePriceFixtureSchema = exports.NetworkSchema = exports.MandateSpecSchema = exports.InterpreterOptionsSchema = exports.InstallPolicyInputSchema = exports.GetInterpreterInfoInputSchema = exports.ComposeUserResponsesSchema = void 0;
26
26
  const run_1 = require("@crediolabs/policy-synth/run");
27
27
  Object.defineProperty(exports, "ComposeUserResponsesSchema", { enumerable: true, get: function () { return run_1.ComposeUserResponsesSchema; } });
28
+ Object.defineProperty(exports, "GetInterpreterInfoInputSchema", { enumerable: true, get: function () { return run_1.GetInterpreterInfoInputSchema; } });
29
+ Object.defineProperty(exports, "InstallPolicyInputSchema", { enumerable: true, get: function () { return run_1.InstallPolicyInputSchema; } });
28
30
  Object.defineProperty(exports, "InterpreterOptionsSchema", { enumerable: true, get: function () { return run_1.InterpreterOptionsSchema; } });
29
31
  Object.defineProperty(exports, "MandateSpecSchema", { enumerable: true, get: function () { return run_1.MandateSpecSchema; } });
30
32
  Object.defineProperty(exports, "NetworkSchema", { enumerable: true, get: function () { return run_1.NetworkSchema; } });
33
+ Object.defineProperty(exports, "OraclePriceFixtureSchema", { enumerable: true, get: function () { return run_1.OraclePriceFixtureSchema; } });
31
34
  Object.defineProperty(exports, "OzAdapterConfigSchema", { enumerable: true, get: function () { return run_1.OzAdapterConfigSchema; } });
35
+ Object.defineProperty(exports, "PredicateNodeSchema", { enumerable: true, get: function () { return run_1.PredicateNodeSchema; } });
32
36
  Object.defineProperty(exports, "RecordedTransactionSchema", { enumerable: true, get: function () { return run_1.RecordedTransactionSchema; } });
33
37
  Object.defineProperty(exports, "RecordTransactionInputSchema", { enumerable: true, get: function () { return run_1.RecordTransactionInputSchema; } });
38
+ Object.defineProperty(exports, "RevokePolicyInputSchema", { enumerable: true, get: function () { return run_1.RevokePolicyInputSchema; } });
39
+ Object.defineProperty(exports, "SimulatePolicyInputSchema", { enumerable: true, get: function () { return run_1.SimulatePolicyInputSchema; } });
34
40
  Object.defineProperty(exports, "SynthesizePolicyInputSchema", { enumerable: true, get: function () { return run_1.SynthesizePolicyInputSchema; } });
35
41
  Object.defineProperty(exports, "ToolErrorSchema", { enumerable: true, get: function () { return run_1.ToolErrorSchema; } });
42
+ Object.defineProperty(exports, "VerifyPolicyInputSchema", { enumerable: true, get: function () { return run_1.VerifyPolicyInputSchema; } });
36
43
  const zod_1 = require("zod");
37
44
  /** Flat ZodRawShape used for the MCP SDK tool registration. The body
38
45
  * re-validates against `RecordTransactionInputSchema` so the mutual-exclusion
@@ -55,4 +62,69 @@ exports.SynthesizePolicyToolShape = {
55
62
  confidenceOverride: zod_1.z.object({ threshold: zod_1.z.number().min(0).max(1) }).optional(),
56
63
  interpreter: run_1.InterpreterOptionsSchema.optional(),
57
64
  ozConfig: run_1.OzAdapterConfigSchema.optional(),
65
+ // Without this the tool chain has no join. A ProposedPolicy carries
66
+ // `policyDocuments[].encodedPredicate` (canonical ScVal bytes), while
67
+ // `simulate_policy` and `verify_policy` both want the PredicateNode TREE,
68
+ // and the tree is only returned under `explain`. Omitting it here left an
69
+ // MCP client able to synthesize a policy and then unable to prove anything
70
+ // about it - the two checks were reachable only with `predicate: null`,
71
+ // which skips the interpreter predicate entirely.
72
+ explain: zod_1.z.boolean().optional(),
73
+ };
74
+ /** Flat ZodRawShape for `simulate_policy`. The `predicate` is typed as
75
+ * `z.unknown()` at the tool boundary because the recursive
76
+ * `PredicateNodeSchema` is a `z.lazy()` union (the SDK does not accept
77
+ * unions at the tool-registration boundary); the body re-validates
78
+ * against `SimulatePolicyInputSchema`, which fails closed on a
79
+ * malformed predicate. `predicate` is nullable here (vs required for
80
+ * verify_policy) so the SDK-emitted JSON Schema mirrors the engine's
81
+ * "OZ-only / no interpreter predicate" contract. */
82
+ exports.SimulatePolicyToolShape = {
83
+ predicate: zod_1.z.unknown().nullable().optional(),
84
+ permitTx: run_1.RecordedTransactionSchema,
85
+ validUntilLedger: zod_1.z.number().int().positive().optional(),
86
+ oraclePricesByAsset: zod_1.z.record(zod_1.z.string(), run_1.OraclePriceFixtureSchema).optional(),
87
+ };
88
+ /** Flat ZodRawShape for `verify_policy`. Same `z.unknown()` boundary
89
+ * trick for `predicate` as `SimulatePolicyToolShape`; `predicate` is
90
+ * required at the strict-schema level (`VerifyPolicyInputSchema`) so
91
+ * the body fails closed on a missing predicate. */
92
+ exports.VerifyPolicyToolShape = {
93
+ predicate: zod_1.z.unknown(),
94
+ permitTx: run_1.RecordedTransactionSchema,
95
+ validUntilLedger: zod_1.z.number().int().positive().optional(),
96
+ oraclePricesByAsset: zod_1.z.record(zod_1.z.string(), run_1.OraclePriceFixtureSchema).optional(),
97
+ };
98
+ /** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
99
+ * at the tool boundary because the rule schema is a discriminated union
100
+ * the SDK does not accept at registration; the body re-validates
101
+ * against `InstallPolicyInputSchema`. `encodedPredicate` /
102
+ * `predicateHash` live INSIDE `rule.policies[].predicateBlobBase64`
103
+ * (the policy already carries the encoded predicate); the run-layer
104
+ * extracts them from there. */
105
+ exports.InstallPolicyToolShape = {
106
+ smartAccount: zod_1.z.string().min(1).optional(),
107
+ sourceAccount: zod_1.z.string().min(1).optional(),
108
+ rule: zod_1.z.unknown().optional(),
109
+ installNonce: zod_1.z.number().int().positive().optional(),
110
+ interpreterAddress: zod_1.z.string().optional(),
111
+ rpcUrl: zod_1.z.string().url().optional(),
112
+ baseFee: zod_1.z.number().int().positive().optional(),
113
+ };
114
+ /** Flat ZodRawShape for `revoke_policy`. */
115
+ exports.RevokePolicyToolShape = {
116
+ smartAccount: zod_1.z.string().min(1).optional(),
117
+ sourceAccount: zod_1.z.string().min(1).optional(),
118
+ ruleId: zod_1.z.number().int().nonnegative().optional(),
119
+ interpreterAddress: zod_1.z.string().optional(),
120
+ rpcUrl: zod_1.z.string().url().optional(),
121
+ baseFee: zod_1.z.number().int().positive().optional(),
122
+ };
123
+ /** Flat ZodRawShape for `get_interpreter_info`. `verifyLive` triggers an
124
+ * optional RPC `grammar_version()` call so the caller can verify the
125
+ * deployed contract matches the pin. */
126
+ exports.GetInterpreterInfoToolShape = {
127
+ network: zod_1.z.enum(['mainnet', 'testnet']).optional(),
128
+ verifyLive: zod_1.z.boolean().optional(),
129
+ rpcUrl: zod_1.z.string().url().optional(),
58
130
  };
@@ -39,4 +39,24 @@ function registerTools(server) {
39
39
  const res = await (0, run_1.runSynthesizePolicy)(args);
40
40
  return (0, result_ts_1.mcpResultFromCore)(res);
41
41
  });
42
+ server.tool('simulate_policy', 'Replay a RecordedTransaction against a proposed PredicateNode (or null for an OZ-only policy) and emit the SimulationResult envelope (permit verdict + deny-case battery). Returns a SIMULATION_ERROR ToolError on runtime evaluation failure.', schemas_ts_1.SimulatePolicyToolShape, async (args) => {
43
+ const res = await (0, run_1.runSimulatePolicy)(args);
44
+ return (0, result_ts_1.mcpResultFromCore)(res);
45
+ });
46
+ 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.', schemas_ts_1.VerifyPolicyToolShape, async (args) => {
47
+ const res = await (0, run_1.runVerifyPolicy)(args);
48
+ return (0, result_ts_1.mcpResultFromCore)(res);
49
+ });
50
+ 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.', schemas_ts_1.InstallPolicyToolShape, async (args) => {
51
+ const res = await (0, run_1.runInstallPolicy)(args);
52
+ return (0, result_ts_1.mcpResultFromCore)(res);
53
+ });
54
+ 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.', schemas_ts_1.RevokePolicyToolShape, async (args) => {
55
+ const res = await (0, run_1.runRevokePolicy)(args);
56
+ return (0, result_ts_1.mcpResultFromCore)(res);
57
+ });
58
+ server.tool('get_interpreter_info', 'Read-only fingerprint lookup for the policy interpreter contract: returns the pinned address, grammar version, and wasm sha256 (from DEPLOYMENTS.md + 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.', schemas_ts_1.GetInterpreterInfoToolShape, async (args) => {
59
+ const res = await (0, run_1.runGetInterpreterInfo)(args);
60
+ return (0, result_ts_1.mcpResultFromCore)(res);
61
+ });
42
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crediolabs/policy-builder-mcp",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
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",
@@ -63,8 +63,9 @@
63
63
  "prepack": "bun run build"
64
64
  },
65
65
  "dependencies": {
66
- "@crediolabs/policy-synth": "0.1.10",
66
+ "@crediolabs/policy-synth": "workspace:*",
67
67
  "@modelcontextprotocol/sdk": "1.18.1",
68
+ "@stellar/stellar-sdk": "14.4.0",
68
69
  "zod": "3.25.76"
69
70
  },
70
71
  "devDependencies": {
package/src/schemas.ts CHANGED
@@ -23,14 +23,21 @@
23
23
 
24
24
  import {
25
25
  ComposeUserResponsesSchema,
26
+ GetInterpreterInfoInputSchema,
27
+ InstallPolicyInputSchema,
26
28
  InterpreterOptionsSchema,
27
29
  MandateSpecSchema,
28
30
  NetworkSchema,
31
+ OraclePriceFixtureSchema,
29
32
  OzAdapterConfigSchema,
33
+ PredicateNodeSchema,
30
34
  RecordedTransactionSchema,
31
35
  RecordTransactionInputSchema,
36
+ RevokePolicyInputSchema,
37
+ SimulatePolicyInputSchema,
32
38
  SynthesizePolicyInputSchema,
33
39
  ToolErrorSchema,
40
+ VerifyPolicyInputSchema,
34
41
  } from '@crediolabs/policy-synth/run'
35
42
  import { z } from 'zod'
36
43
 
@@ -41,14 +48,21 @@ import { z } from 'zod'
41
48
  // package directly.
42
49
  export {
43
50
  ComposeUserResponsesSchema,
51
+ GetInterpreterInfoInputSchema,
52
+ InstallPolicyInputSchema,
44
53
  InterpreterOptionsSchema,
45
54
  MandateSpecSchema,
46
55
  NetworkSchema,
56
+ OraclePriceFixtureSchema,
47
57
  OzAdapterConfigSchema,
58
+ PredicateNodeSchema,
48
59
  RecordedTransactionSchema,
49
60
  RecordTransactionInputSchema,
61
+ RevokePolicyInputSchema,
62
+ SimulatePolicyInputSchema,
50
63
  SynthesizePolicyInputSchema,
51
64
  ToolErrorSchema,
65
+ VerifyPolicyInputSchema,
52
66
  }
53
67
 
54
68
  /** Flat ZodRawShape used for the MCP SDK tool registration. The body
@@ -73,4 +87,85 @@ export const SynthesizePolicyToolShape = {
73
87
  confidenceOverride: z.object({ threshold: z.number().min(0).max(1) }).optional(),
74
88
  interpreter: InterpreterOptionsSchema.optional(),
75
89
  ozConfig: OzAdapterConfigSchema.optional(),
90
+ // Without this the tool chain has no join. A ProposedPolicy carries
91
+ // `policyDocuments[].encodedPredicate` (canonical ScVal bytes), while
92
+ // `simulate_policy` and `verify_policy` both want the PredicateNode TREE,
93
+ // and the tree is only returned under `explain`. Omitting it here left an
94
+ // MCP client able to synthesize a policy and then unable to prove anything
95
+ // about it - the two checks were reachable only with `predicate: null`,
96
+ // which skips the interpreter predicate entirely.
97
+ explain: z.boolean().optional(),
98
+ } as const
99
+
100
+ /** Flat ZodRawShape for `simulate_policy`. The `predicate` is typed as
101
+ * `z.unknown()` at the tool boundary because the recursive
102
+ * `PredicateNodeSchema` is a `z.lazy()` union (the SDK does not accept
103
+ * unions at the tool-registration boundary); the body re-validates
104
+ * against `SimulatePolicyInputSchema`, which fails closed on a
105
+ * malformed predicate. `predicate` is nullable here (vs required for
106
+ * verify_policy) so the SDK-emitted JSON Schema mirrors the engine's
107
+ * "OZ-only / no interpreter predicate" contract. */
108
+ export const SimulatePolicyToolShape = {
109
+ predicate: z.unknown().nullable().optional(),
110
+ permitTx: RecordedTransactionSchema,
111
+ validUntilLedger: z.number().int().positive().optional(),
112
+ oraclePricesByAsset: z.record(z.string(), OraclePriceFixtureSchema).optional(),
113
+ } as const
114
+
115
+ /** Flat ZodRawShape for `verify_policy`. Same `z.unknown()` boundary
116
+ * trick for `predicate` as `SimulatePolicyToolShape`; `predicate` is
117
+ * required at the strict-schema level (`VerifyPolicyInputSchema`) so
118
+ * the body fails closed on a missing predicate. */
119
+ export const VerifyPolicyToolShape = {
120
+ predicate: z.unknown(),
121
+ permitTx: RecordedTransactionSchema,
122
+ validUntilLedger: z.number().int().positive().optional(),
123
+ oraclePricesByAsset: z.record(z.string(), OraclePriceFixtureSchema).optional(),
124
+ } as const
125
+
126
+ // Re-export the strict input schemas so MCP consumers (and downstream
127
+ // tests) can import the canonical wire shapes from the same module
128
+ // that owns the tool-shape glue.
129
+ export type {
130
+ GetInterpreterInfoInput,
131
+ InstallPolicyInput,
132
+ RevokePolicyInput,
133
+ SimulatePolicyInput,
134
+ VerifyPolicyInput,
135
+ } from '@crediolabs/policy-synth/run'
136
+
137
+ /** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
138
+ * at the tool boundary because the rule schema is a discriminated union
139
+ * the SDK does not accept at registration; the body re-validates
140
+ * against `InstallPolicyInputSchema`. `encodedPredicate` /
141
+ * `predicateHash` live INSIDE `rule.policies[].predicateBlobBase64`
142
+ * (the policy already carries the encoded predicate); the run-layer
143
+ * extracts them from there. */
144
+ export const InstallPolicyToolShape = {
145
+ smartAccount: z.string().min(1).optional(),
146
+ sourceAccount: z.string().min(1).optional(),
147
+ rule: z.unknown().optional(),
148
+ installNonce: z.number().int().positive().optional(),
149
+ interpreterAddress: z.string().optional(),
150
+ rpcUrl: z.string().url().optional(),
151
+ baseFee: z.number().int().positive().optional(),
152
+ } as const
153
+
154
+ /** Flat ZodRawShape for `revoke_policy`. */
155
+ export const RevokePolicyToolShape = {
156
+ smartAccount: z.string().min(1).optional(),
157
+ sourceAccount: z.string().min(1).optional(),
158
+ ruleId: z.number().int().nonnegative().optional(),
159
+ interpreterAddress: z.string().optional(),
160
+ rpcUrl: z.string().url().optional(),
161
+ baseFee: z.number().int().positive().optional(),
162
+ } as const
163
+
164
+ /** Flat ZodRawShape for `get_interpreter_info`. `verifyLive` triggers an
165
+ * optional RPC `grammar_version()` call so the caller can verify the
166
+ * deployed contract matches the pin. */
167
+ export const GetInterpreterInfoToolShape = {
168
+ network: z.enum(['mainnet', 'testnet']).optional(),
169
+ verifyLive: z.boolean().optional(),
170
+ rpcUrl: z.string().url().optional(),
76
171
  } as const
package/src/server.ts CHANGED
@@ -11,10 +11,26 @@
11
11
  // shared mutable state across calls; nothing here caches, queues, or holds
12
12
  // key material.
13
13
 
14
- import { runRecordTransaction, runSynthesizePolicy } from '@crediolabs/policy-synth/run'
14
+ import {
15
+ runGetInterpreterInfo,
16
+ runInstallPolicy,
17
+ runRecordTransaction,
18
+ runRevokePolicy,
19
+ runSimulatePolicy,
20
+ runSynthesizePolicy,
21
+ runVerifyPolicy,
22
+ } from '@crediolabs/policy-synth/run'
15
23
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
16
24
  import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
17
- import { RecordTransactionToolShape, SynthesizePolicyToolShape } from './schemas.ts'
25
+ import {
26
+ GetInterpreterInfoToolShape,
27
+ InstallPolicyToolShape,
28
+ RecordTransactionToolShape,
29
+ RevokePolicyToolShape,
30
+ SimulatePolicyToolShape,
31
+ SynthesizePolicyToolShape,
32
+ VerifyPolicyToolShape,
33
+ } from './schemas.ts'
18
34
  import { mcpResultFromCore } from './tools/result.ts'
19
35
 
20
36
  /** Build a fresh, stateless MCP server. The caller owns the returned object
@@ -53,4 +69,54 @@ export function registerTools(server: McpServer): void {
53
69
  return mcpResultFromCore(res) as unknown as CallToolResult
54
70
  }
55
71
  )
72
+
73
+ server.tool(
74
+ 'simulate_policy',
75
+ 'Replay a RecordedTransaction against a proposed PredicateNode (or null for an OZ-only policy) and emit the SimulationResult envelope (permit verdict + deny-case battery). Returns a SIMULATION_ERROR ToolError on runtime evaluation failure.',
76
+ SimulatePolicyToolShape,
77
+ async (args) => {
78
+ const res = await runSimulatePolicy(args)
79
+ return mcpResultFromCore(res) as unknown as CallToolResult
80
+ }
81
+ )
82
+
83
+ server.tool(
84
+ 'verify_policy',
85
+ '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.',
86
+ VerifyPolicyToolShape,
87
+ async (args) => {
88
+ const res = await runVerifyPolicy(args)
89
+ return mcpResultFromCore(res) as unknown as CallToolResult
90
+ }
91
+ )
92
+
93
+ server.tool(
94
+ 'install_policy',
95
+ '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.',
96
+ InstallPolicyToolShape,
97
+ async (args) => {
98
+ const res = await runInstallPolicy(args)
99
+ return mcpResultFromCore(res) as unknown as CallToolResult
100
+ }
101
+ )
102
+
103
+ server.tool(
104
+ 'revoke_policy',
105
+ '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.',
106
+ RevokePolicyToolShape,
107
+ async (args) => {
108
+ const res = await runRevokePolicy(args)
109
+ return mcpResultFromCore(res) as unknown as CallToolResult
110
+ }
111
+ )
112
+
113
+ server.tool(
114
+ 'get_interpreter_info',
115
+ 'Read-only fingerprint lookup for the policy interpreter contract: returns the pinned address, grammar version, and wasm sha256 (from DEPLOYMENTS.md + 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.',
116
+ GetInterpreterInfoToolShape,
117
+ async (args) => {
118
+ const res = await runGetInterpreterInfo(args)
119
+ return mcpResultFromCore(res) as unknown as CallToolResult
120
+ }
121
+ )
56
122
  }