@crediolabs/policy-builder-mcp 0.3.1 → 0.4.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.
@@ -834,6 +834,21 @@ export declare const VerifyPolicyToolShape: {
834
834
  readonly validUntilLedger: z.ZodOptional<z.ZodNumber>;
835
835
  };
836
836
  export type { GetInterpreterInfoInput, InstallPolicyInput, RevokePolicyInput, SimulatePolicyInput, VerifyPolicyInput, } from '@crediolabs/policy-synth/run';
837
+ /** Flat ZodRawShape for `declare_policy`. The DECLARATIVE front-end: the
838
+ * constraint stated outright, with no transaction to decode. Every field is
839
+ * optional except `fn`; the body re-validates against the strict
840
+ * `DeclarePolicyInputSchema`, which is `.strict()` and so refuses the removed
841
+ * MandateSpec fields (`spendingLimit`, `approvalThreshold`) rather than
842
+ * accepting them in silence. */
843
+ export declare const DeclarePolicyToolShape: {
844
+ readonly fn: z.ZodString;
845
+ readonly contract: z.ZodOptional<z.ZodString>;
846
+ readonly maxAmount: z.ZodOptional<z.ZodString>;
847
+ readonly amountArgIndex: z.ZodOptional<z.ZodNumber>;
848
+ readonly recipients: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
849
+ readonly recipientArgIndex: z.ZodOptional<z.ZodNumber>;
850
+ readonly allowZeroCap: z.ZodOptional<z.ZodBoolean>;
851
+ };
837
852
  /** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
838
853
  * at the tool boundary because the rule schema is a discriminated union
839
854
  * the SDK does not accept at registration; the body re-validates
@@ -842,6 +857,10 @@ export type { GetInterpreterInfoInput, InstallPolicyInput, RevokePolicyInput, Si
842
857
  * (the policy already carries the encoded predicate); the run-layer
843
858
  * extracts them from there. */
844
859
  export declare const InstallPolicyToolShape: {
860
+ /** Rules already on the account. Supplying them turns on the cross-rule
861
+ * authority scan; omitting them returns `authorityScan: null`, which means
862
+ * "not checked" rather than "nothing found". */
863
+ readonly existingRules: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
845
864
  readonly rule: z.ZodOptional<z.ZodUnknown>;
846
865
  readonly installNonce: z.ZodOptional<z.ZodNumber>;
847
866
  readonly interpreterAddress: z.ZodOptional<z.ZodString>;
@@ -84,6 +84,21 @@ const SmartAccountToolShape = {
84
84
  rpcUrl: z.string().url().optional(),
85
85
  baseFee: z.number().int().positive().optional(),
86
86
  };
87
+ /** Flat ZodRawShape for `declare_policy`. The DECLARATIVE front-end: the
88
+ * constraint stated outright, with no transaction to decode. Every field is
89
+ * optional except `fn`; the body re-validates against the strict
90
+ * `DeclarePolicyInputSchema`, which is `.strict()` and so refuses the removed
91
+ * MandateSpec fields (`spendingLimit`, `approvalThreshold`) rather than
92
+ * accepting them in silence. */
93
+ export const DeclarePolicyToolShape = {
94
+ fn: z.string().min(1),
95
+ contract: z.string().optional(),
96
+ maxAmount: z.string().optional(),
97
+ amountArgIndex: z.number().int().nonnegative().optional(),
98
+ recipients: z.array(z.string()).optional(),
99
+ recipientArgIndex: z.number().int().nonnegative().optional(),
100
+ allowZeroCap: z.boolean().optional(),
101
+ };
87
102
  /** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
88
103
  * at the tool boundary because the rule schema is a discriminated union
89
104
  * the SDK does not accept at registration; the body re-validates
@@ -93,6 +108,10 @@ const SmartAccountToolShape = {
93
108
  * extracts them from there. */
94
109
  export const InstallPolicyToolShape = {
95
110
  ...SmartAccountToolShape,
111
+ /** Rules already on the account. Supplying them turns on the cross-rule
112
+ * authority scan; omitting them returns `authorityScan: null`, which means
113
+ * "not checked" rather than "nothing found". */
114
+ existingRules: z.array(z.unknown()).optional(),
96
115
  rule: z.unknown().optional(),
97
116
  installNonce: z.number().int().positive().optional(),
98
117
  interpreterAddress: z.string().optional(),
@@ -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 { runDeclarePolicy, runGetInterpreterInfo, runInstallPolicy, 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 { DeclarePolicyToolShape, GetInterpreterInfoToolShape, InstallPolicyToolShape, 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
@@ -32,7 +32,8 @@ export function registerTools(server) {
32
32
  server.tool('synthesize_policy', 'Synthesize a ProposedPolicy from a RecordedTransaction (`source: recording`).', SynthesizePolicyToolShape, (args) => runSynthesizePolicy(args).then(toCallToolResult));
33
33
  server.tool('simulate_policy', 'Evaluate a predicate against one recorded call and report permit/deny with the deny reason. The evaluator is a second implementation of the on-chain semantics, cross-checked against the Rust interpreter by the conformance harness, so a verdict here is a claim about what the contract would do. Pass the `predicate` returned by `synthesize_policy` under `explain`.', SimulatePolicyToolShape, (args) => toCallToolResult(runSimulatePolicy(args)));
34
34
  server.tool('verify_policy', 'Check a predicate against the transaction it was synthesised from, plus a generated deny case per dimension. Reports `ok` only when the permit case is permitted AND every deny case is denied - a denied permit case means the policy is too strict, a permitted deny case means it is too loose.', VerifyPolicyToolShape, (args) => toCallToolResult(runVerifyPolicy(args)));
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));
35
+ server.tool('declare_policy', 'Build an interpreter predicate from a DECLARED constraint - the method to pin, and optionally the contract, a per-call amount cap and a recipient allowlist. Use this when there is no transaction to record, or when `record_transaction` refuses a contract it does not recognise. Returns the predicate tree, its canonical encoding and hash, ready for `install_policy`. `warnings` names any argument index that was GUESSED rather than supplied - a bound on the wrong argument constrains nothing while looking correct, so read them. There is no rolling spend window and no approval threshold: neither is expressible in grammar 3.', DeclarePolicyToolShape, (args) => Promise.resolve(runDeclarePolicy(args)).then(toCallToolResult));
36
+ 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. Pass `existingRules` to get an `authorityScan`: every rule already on the account that a signer of this install could name INSTEAD, including an unpoliced one against which the predicate never runs - a predicate only constrains a key when the policed rule is the only rule that key is on. Omitting them returns `authorityScan: null`, meaning NOT CHECKED. 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
37
  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
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
  }
@@ -834,6 +834,21 @@ export declare const VerifyPolicyToolShape: {
834
834
  readonly validUntilLedger: z.ZodOptional<z.ZodNumber>;
835
835
  };
836
836
  export type { GetInterpreterInfoInput, InstallPolicyInput, RevokePolicyInput, SimulatePolicyInput, VerifyPolicyInput, } from '@crediolabs/policy-synth/run';
837
+ /** Flat ZodRawShape for `declare_policy`. The DECLARATIVE front-end: the
838
+ * constraint stated outright, with no transaction to decode. Every field is
839
+ * optional except `fn`; the body re-validates against the strict
840
+ * `DeclarePolicyInputSchema`, which is `.strict()` and so refuses the removed
841
+ * MandateSpec fields (`spendingLimit`, `approvalThreshold`) rather than
842
+ * accepting them in silence. */
843
+ export declare const DeclarePolicyToolShape: {
844
+ readonly fn: z.ZodString;
845
+ readonly contract: z.ZodOptional<z.ZodString>;
846
+ readonly maxAmount: z.ZodOptional<z.ZodString>;
847
+ readonly amountArgIndex: z.ZodOptional<z.ZodNumber>;
848
+ readonly recipients: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
849
+ readonly recipientArgIndex: z.ZodOptional<z.ZodNumber>;
850
+ readonly allowZeroCap: z.ZodOptional<z.ZodBoolean>;
851
+ };
837
852
  /** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
838
853
  * at the tool boundary because the rule schema is a discriminated union
839
854
  * the SDK does not accept at registration; the body re-validates
@@ -842,6 +857,10 @@ export type { GetInterpreterInfoInput, InstallPolicyInput, RevokePolicyInput, Si
842
857
  * (the policy already carries the encoded predicate); the run-layer
843
858
  * extracts them from there. */
844
859
  export declare const InstallPolicyToolShape: {
860
+ /** Rules already on the account. Supplying them turns on the cross-rule
861
+ * authority scan; omitting them returns `authorityScan: null`, which means
862
+ * "not checked" rather than "nothing found". */
863
+ readonly existingRules: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
845
864
  readonly rule: z.ZodOptional<z.ZodUnknown>;
846
865
  readonly installNonce: z.ZodOptional<z.ZodNumber>;
847
866
  readonly interpreterAddress: z.ZodOptional<z.ZodString>;
@@ -22,7 +22,7 @@
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.GetInterpreterInfoToolShape = exports.RevokePolicyToolShape = exports.InstallPolicyToolShape = exports.VerifyPolicyToolShape = exports.SimulatePolicyToolShape = exports.SynthesizePolicyToolShape = exports.RecordTransactionToolShape = exports.ToolErrorSchema = exports.SynthesizePolicyInputSchema = exports.RevokePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.PredicateNodeSchema = exports.NetworkSchema = exports.InterpreterOptionsSchema = exports.InstallPolicyInputSchema = exports.GetInterpreterInfoInputSchema = exports.ComposeUserResponsesSchema = void 0;
25
+ exports.GetInterpreterInfoToolShape = exports.RevokePolicyToolShape = exports.InstallPolicyToolShape = exports.DeclarePolicyToolShape = exports.VerifyPolicyToolShape = exports.SimulatePolicyToolShape = exports.SynthesizePolicyToolShape = exports.RecordTransactionToolShape = exports.ToolErrorSchema = exports.SynthesizePolicyInputSchema = exports.RevokePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.PredicateNodeSchema = exports.NetworkSchema = 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
28
  Object.defineProperty(exports, "GetInterpreterInfoInputSchema", { enumerable: true, get: function () { return run_1.GetInterpreterInfoInputSchema; } });
@@ -92,6 +92,21 @@ const SmartAccountToolShape = {
92
92
  rpcUrl: zod_1.z.string().url().optional(),
93
93
  baseFee: zod_1.z.number().int().positive().optional(),
94
94
  };
95
+ /** Flat ZodRawShape for `declare_policy`. The DECLARATIVE front-end: the
96
+ * constraint stated outright, with no transaction to decode. Every field is
97
+ * optional except `fn`; the body re-validates against the strict
98
+ * `DeclarePolicyInputSchema`, which is `.strict()` and so refuses the removed
99
+ * MandateSpec fields (`spendingLimit`, `approvalThreshold`) rather than
100
+ * accepting them in silence. */
101
+ exports.DeclarePolicyToolShape = {
102
+ fn: zod_1.z.string().min(1),
103
+ contract: zod_1.z.string().optional(),
104
+ maxAmount: zod_1.z.string().optional(),
105
+ amountArgIndex: zod_1.z.number().int().nonnegative().optional(),
106
+ recipients: zod_1.z.array(zod_1.z.string()).optional(),
107
+ recipientArgIndex: zod_1.z.number().int().nonnegative().optional(),
108
+ allowZeroCap: zod_1.z.boolean().optional(),
109
+ };
95
110
  /** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
96
111
  * at the tool boundary because the rule schema is a discriminated union
97
112
  * the SDK does not accept at registration; the body re-validates
@@ -101,6 +116,10 @@ const SmartAccountToolShape = {
101
116
  * extracts them from there. */
102
117
  exports.InstallPolicyToolShape = {
103
118
  ...SmartAccountToolShape,
119
+ /** Rules already on the account. Supplying them turns on the cross-rule
120
+ * authority scan; omitting them returns `authorityScan: null`, which means
121
+ * "not checked" rather than "nothing found". */
122
+ existingRules: zod_1.z.array(zod_1.z.unknown()).optional(),
104
123
  rule: zod_1.z.unknown().optional(),
105
124
  installNonce: zod_1.z.number().int().positive().optional(),
106
125
  interpreterAddress: zod_1.z.string().optional(),
@@ -36,7 +36,8 @@ function registerTools(server) {
36
36
  server.tool('synthesize_policy', 'Synthesize a ProposedPolicy from a RecordedTransaction (`source: recording`).', schemas_ts_1.SynthesizePolicyToolShape, (args) => (0, run_1.runSynthesizePolicy)(args).then(toCallToolResult));
37
37
  server.tool('simulate_policy', 'Evaluate a predicate against one recorded call and report permit/deny with the deny reason. The evaluator is a second implementation of the on-chain semantics, cross-checked against the Rust interpreter by the conformance harness, so a verdict here is a claim about what the contract would do. Pass the `predicate` returned by `synthesize_policy` under `explain`.', schemas_ts_1.SimulatePolicyToolShape, (args) => toCallToolResult((0, run_1.runSimulatePolicy)(args)));
38
38
  server.tool('verify_policy', 'Check a predicate against the transaction it was synthesised from, plus a generated deny case per dimension. Reports `ok` only when the permit case is permitted AND every deny case is denied - a denied permit case means the policy is too strict, a permitted deny case means it is too loose.', schemas_ts_1.VerifyPolicyToolShape, (args) => toCallToolResult((0, run_1.runVerifyPolicy)(args)));
39
- 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, (args) => (0, run_1.runInstallPolicy)(args).then(toCallToolResult));
39
+ server.tool('declare_policy', 'Build an interpreter predicate from a DECLARED constraint - the method to pin, and optionally the contract, a per-call amount cap and a recipient allowlist. Use this when there is no transaction to record, or when `record_transaction` refuses a contract it does not recognise. Returns the predicate tree, its canonical encoding and hash, ready for `install_policy`. `warnings` names any argument index that was GUESSED rather than supplied - a bound on the wrong argument constrains nothing while looking correct, so read them. There is no rolling spend window and no approval threshold: neither is expressible in grammar 3.', schemas_ts_1.DeclarePolicyToolShape, (args) => Promise.resolve((0, run_1.runDeclarePolicy)(args)).then(toCallToolResult));
40
+ 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. Pass `existingRules` to get an `authorityScan`: every rule already on the account that a signer of this install could name INSTEAD, including an unpoliced one against which the predicate never runs - a predicate only constrains a key when the policed rule is the only rule that key is on. Omitting them returns `authorityScan: null`, meaning NOT CHECKED. 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, (args) => (0, run_1.runInstallPolicy)(args).then(toCallToolResult));
40
41
  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, (args) => (0, run_1.runRevokePolicy)(args).then(toCallToolResult));
41
42
  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.', schemas_ts_1.GetInterpreterInfoToolShape, (args) => (0, run_1.runGetInterpreterInfo)(args).then(toCallToolResult));
42
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crediolabs/policy-builder-mcp",
3
- "version": "0.3.1",
3
+ "version": "0.4.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",
@@ -63,7 +63,7 @@
63
63
  "prepack": "bun run build"
64
64
  },
65
65
  "dependencies": {
66
- "@crediolabs/policy-synth": "0.3.1",
66
+ "@crediolabs/policy-synth": "0.4.0",
67
67
  "@modelcontextprotocol/sdk": "1.30.0",
68
68
  "@stellar/stellar-sdk": "14.4.0",
69
69
  "zod": "3.25.76"
package/src/schemas.ts CHANGED
@@ -124,6 +124,22 @@ const SmartAccountToolShape = {
124
124
  baseFee: z.number().int().positive().optional(),
125
125
  }
126
126
 
127
+ /** Flat ZodRawShape for `declare_policy`. The DECLARATIVE front-end: the
128
+ * constraint stated outright, with no transaction to decode. Every field is
129
+ * optional except `fn`; the body re-validates against the strict
130
+ * `DeclarePolicyInputSchema`, which is `.strict()` and so refuses the removed
131
+ * MandateSpec fields (`spendingLimit`, `approvalThreshold`) rather than
132
+ * accepting them in silence. */
133
+ export const DeclarePolicyToolShape = {
134
+ fn: z.string().min(1),
135
+ contract: z.string().optional(),
136
+ maxAmount: z.string().optional(),
137
+ amountArgIndex: z.number().int().nonnegative().optional(),
138
+ recipients: z.array(z.string()).optional(),
139
+ recipientArgIndex: z.number().int().nonnegative().optional(),
140
+ allowZeroCap: z.boolean().optional(),
141
+ } as const
142
+
127
143
  /** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
128
144
  * at the tool boundary because the rule schema is a discriminated union
129
145
  * the SDK does not accept at registration; the body re-validates
@@ -133,6 +149,10 @@ const SmartAccountToolShape = {
133
149
  * extracts them from there. */
134
150
  export const InstallPolicyToolShape = {
135
151
  ...SmartAccountToolShape,
152
+ /** Rules already on the account. Supplying them turns on the cross-rule
153
+ * authority scan; omitting them returns `authorityScan: null`, which means
154
+ * "not checked" rather than "nothing found". */
155
+ existingRules: z.array(z.unknown()).optional(),
136
156
  rule: z.unknown().optional(),
137
157
  installNonce: z.number().int().positive().optional(),
138
158
  interpreterAddress: z.string().optional(),
package/src/server.ts CHANGED
@@ -13,6 +13,7 @@
13
13
 
14
14
  import type { ToolResponse } from '@crediolabs/policy-synth'
15
15
  import {
16
+ runDeclarePolicy,
16
17
  runGetInterpreterInfo,
17
18
  runInstallPolicy,
18
19
  runRecordTransaction,
@@ -24,6 +25,7 @@ import {
24
25
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
25
26
  import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
26
27
  import {
28
+ DeclarePolicyToolShape,
27
29
  GetInterpreterInfoToolShape,
28
30
  InstallPolicyToolShape,
29
31
  RecordTransactionToolShape,
@@ -83,9 +85,16 @@ export function registerTools(server: McpServer): void {
83
85
  (args) => toCallToolResult(runVerifyPolicy(args))
84
86
  )
85
87
 
88
+ server.tool(
89
+ 'declare_policy',
90
+ 'Build an interpreter predicate from a DECLARED constraint - the method to pin, and optionally the contract, a per-call amount cap and a recipient allowlist. Use this when there is no transaction to record, or when `record_transaction` refuses a contract it does not recognise. Returns the predicate tree, its canonical encoding and hash, ready for `install_policy`. `warnings` names any argument index that was GUESSED rather than supplied - a bound on the wrong argument constrains nothing while looking correct, so read them. There is no rolling spend window and no approval threshold: neither is expressible in grammar 3.',
91
+ DeclarePolicyToolShape,
92
+ (args) => Promise.resolve(runDeclarePolicy(args)).then(toCallToolResult)
93
+ )
94
+
86
95
  server.tool(
87
96
  'install_policy',
88
- '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.',
97
+ 'Build an UNSIGNED Soroban transaction XDR for `account.add_context_rule(...)` that installs a new policy rule on the given smart account. Pass `existingRules` to get an `authorityScan`: every rule already on the account that a signer of this install could name INSTEAD, including an unpoliced one against which the predicate never runs - a predicate only constrains a key when the policed rule is the only rule that key is on. Omitting them returns `authorityScan: null`, meaning NOT CHECKED. 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.',
89
98
  InstallPolicyToolShape,
90
99
  (args) => runInstallPolicy(args).then(toCallToolResult)
91
100
  )