@crediolabs/policy-builder-mcp 0.3.0 → 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.
- package/dist/src/schemas.d.ts +21 -0
- package/dist/src/schemas.js +28 -2
- package/dist/src/server.js +4 -3
- package/dist-cjs/src/schemas.d.ts +21 -0
- package/dist-cjs/src/schemas.js +29 -3
- package/dist-cjs/src/server.js +2 -1
- package/package.json +3 -3
- package/src/schemas.ts +29 -2
- package/src/server.ts +10 -1
package/dist/src/schemas.d.ts
CHANGED
|
@@ -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,11 +857,16 @@ 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>;
|
|
848
867
|
readonly smartAccount: z.ZodOptional<z.ZodString>;
|
|
849
868
|
readonly sourceAccount: z.ZodOptional<z.ZodString>;
|
|
869
|
+
readonly network: z.ZodOptional<z.ZodEnum<["mainnet", "testnet"]>>;
|
|
850
870
|
readonly rpcUrl: z.ZodOptional<z.ZodString>;
|
|
851
871
|
readonly baseFee: z.ZodOptional<z.ZodNumber>;
|
|
852
872
|
};
|
|
@@ -856,6 +876,7 @@ export declare const RevokePolicyToolShape: {
|
|
|
856
876
|
readonly interpreterAddress: z.ZodOptional<z.ZodString>;
|
|
857
877
|
readonly smartAccount: z.ZodOptional<z.ZodString>;
|
|
858
878
|
readonly sourceAccount: z.ZodOptional<z.ZodString>;
|
|
879
|
+
readonly network: z.ZodOptional<z.ZodEnum<["mainnet", "testnet"]>>;
|
|
859
880
|
readonly rpcUrl: z.ZodOptional<z.ZodString>;
|
|
860
881
|
readonly baseFee: z.ZodOptional<z.ZodNumber>;
|
|
861
882
|
};
|
package/dist/src/schemas.js
CHANGED
|
@@ -67,16 +67,38 @@ const PolicyCheckToolShape = {
|
|
|
67
67
|
export const SimulatePolicyToolShape = { ...PolicyCheckToolShape };
|
|
68
68
|
export const VerifyPolicyToolShape = { ...PolicyCheckToolShape };
|
|
69
69
|
/** Common base for `install_policy` and `revoke_policy`: smartAccount,
|
|
70
|
-
* sourceAccount, optional RPC URL, optional base fee. Both
|
|
71
|
-
* same smart-account context, so the SDK-emitted JSON Schema stays
|
|
70
|
+
* sourceAccount, target network, optional RPC URL, optional base fee. Both
|
|
71
|
+
* share the same smart-account context, so the SDK-emitted JSON Schema stays
|
|
72
72
|
* identical for those fields. The body re-validates against the strict
|
|
73
73
|
* schemas in `@crediolabs/policy-synth/run`. */
|
|
74
74
|
const SmartAccountToolShape = {
|
|
75
75
|
smartAccount: z.string().min(1).optional(),
|
|
76
76
|
sourceAccount: z.string().min(1).optional(),
|
|
77
|
+
/** Omitting this from the shape made both tools testnet-ONLY. The input
|
|
78
|
+
* schema defaults `network` to `testnet` and expects a mainnet caller to
|
|
79
|
+
* set it, but a field absent from the tool shape is STRIPPED before the
|
|
80
|
+
* body runs, so an MCP client could not reach the mainnet pin at all. The
|
|
81
|
+
* failure presented as a deliberate testnet pin rather than as a missing
|
|
82
|
+
* parameter, which sent integrators to build the install by hand. */
|
|
83
|
+
network: NetworkSchema.optional(),
|
|
77
84
|
rpcUrl: z.string().url().optional(),
|
|
78
85
|
baseFee: z.number().int().positive().optional(),
|
|
79
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
|
+
};
|
|
80
102
|
/** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
|
|
81
103
|
* at the tool boundary because the rule schema is a discriminated union
|
|
82
104
|
* the SDK does not accept at registration; the body re-validates
|
|
@@ -86,6 +108,10 @@ const SmartAccountToolShape = {
|
|
|
86
108
|
* extracts them from there. */
|
|
87
109
|
export const InstallPolicyToolShape = {
|
|
88
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(),
|
|
89
115
|
rule: z.unknown().optional(),
|
|
90
116
|
installNonce: z.number().int().positive().optional(),
|
|
91
117
|
interpreterAddress: z.string().optional(),
|
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 { 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('
|
|
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,11 +857,16 @@ 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>;
|
|
848
867
|
readonly smartAccount: z.ZodOptional<z.ZodString>;
|
|
849
868
|
readonly sourceAccount: z.ZodOptional<z.ZodString>;
|
|
869
|
+
readonly network: z.ZodOptional<z.ZodEnum<["mainnet", "testnet"]>>;
|
|
850
870
|
readonly rpcUrl: z.ZodOptional<z.ZodString>;
|
|
851
871
|
readonly baseFee: z.ZodOptional<z.ZodNumber>;
|
|
852
872
|
};
|
|
@@ -856,6 +876,7 @@ export declare const RevokePolicyToolShape: {
|
|
|
856
876
|
readonly interpreterAddress: z.ZodOptional<z.ZodString>;
|
|
857
877
|
readonly smartAccount: z.ZodOptional<z.ZodString>;
|
|
858
878
|
readonly sourceAccount: z.ZodOptional<z.ZodString>;
|
|
879
|
+
readonly network: z.ZodOptional<z.ZodEnum<["mainnet", "testnet"]>>;
|
|
859
880
|
readonly rpcUrl: z.ZodOptional<z.ZodString>;
|
|
860
881
|
readonly baseFee: z.ZodOptional<z.ZodNumber>;
|
|
861
882
|
};
|
package/dist-cjs/src/schemas.js
CHANGED
|
@@ -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; } });
|
|
@@ -75,16 +75,38 @@ const PolicyCheckToolShape = {
|
|
|
75
75
|
exports.SimulatePolicyToolShape = { ...PolicyCheckToolShape };
|
|
76
76
|
exports.VerifyPolicyToolShape = { ...PolicyCheckToolShape };
|
|
77
77
|
/** Common base for `install_policy` and `revoke_policy`: smartAccount,
|
|
78
|
-
* sourceAccount, optional RPC URL, optional base fee. Both
|
|
79
|
-
* same smart-account context, so the SDK-emitted JSON Schema stays
|
|
78
|
+
* sourceAccount, target network, optional RPC URL, optional base fee. Both
|
|
79
|
+
* share the same smart-account context, so the SDK-emitted JSON Schema stays
|
|
80
80
|
* identical for those fields. The body re-validates against the strict
|
|
81
81
|
* schemas in `@crediolabs/policy-synth/run`. */
|
|
82
82
|
const SmartAccountToolShape = {
|
|
83
83
|
smartAccount: zod_1.z.string().min(1).optional(),
|
|
84
84
|
sourceAccount: zod_1.z.string().min(1).optional(),
|
|
85
|
+
/** Omitting this from the shape made both tools testnet-ONLY. The input
|
|
86
|
+
* schema defaults `network` to `testnet` and expects a mainnet caller to
|
|
87
|
+
* set it, but a field absent from the tool shape is STRIPPED before the
|
|
88
|
+
* body runs, so an MCP client could not reach the mainnet pin at all. The
|
|
89
|
+
* failure presented as a deliberate testnet pin rather than as a missing
|
|
90
|
+
* parameter, which sent integrators to build the install by hand. */
|
|
91
|
+
network: run_1.NetworkSchema.optional(),
|
|
85
92
|
rpcUrl: zod_1.z.string().url().optional(),
|
|
86
93
|
baseFee: zod_1.z.number().int().positive().optional(),
|
|
87
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
|
+
};
|
|
88
110
|
/** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
|
|
89
111
|
* at the tool boundary because the rule schema is a discriminated union
|
|
90
112
|
* the SDK does not accept at registration; the body re-validates
|
|
@@ -94,6 +116,10 @@ const SmartAccountToolShape = {
|
|
|
94
116
|
* extracts them from there. */
|
|
95
117
|
exports.InstallPolicyToolShape = {
|
|
96
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(),
|
|
97
123
|
rule: zod_1.z.unknown().optional(),
|
|
98
124
|
installNonce: zod_1.z.number().int().positive().optional(),
|
|
99
125
|
interpreterAddress: zod_1.z.string().optional(),
|
package/dist-cjs/src/server.js
CHANGED
|
@@ -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('
|
|
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
|
+
"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",
|
|
@@ -56,14 +56,14 @@
|
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"test": "bun test",
|
|
59
|
-
"build": "tsc -p tsconfig.build.json && tsc -p tsconfig.build.cjs.json && node scripts/write-cjs-package-json.cjs",
|
|
59
|
+
"build": "rm -rf dist dist-cjs && tsc -p tsconfig.build.json && tsc -p tsconfig.build.cjs.json && node scripts/write-cjs-package-json.cjs",
|
|
60
60
|
"build:esm": "tsc -p tsconfig.build.json",
|
|
61
61
|
"build:cjs": "tsc -p tsconfig.build.cjs.json && node scripts/write-cjs-package-json.cjs",
|
|
62
62
|
"prepublishOnly": "bun run build && bun test",
|
|
63
63
|
"prepack": "bun run build"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@crediolabs/policy-synth": "0.
|
|
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
|
@@ -106,17 +106,40 @@ export type {
|
|
|
106
106
|
} from '@crediolabs/policy-synth/run'
|
|
107
107
|
|
|
108
108
|
/** Common base for `install_policy` and `revoke_policy`: smartAccount,
|
|
109
|
-
* sourceAccount, optional RPC URL, optional base fee. Both
|
|
110
|
-
* same smart-account context, so the SDK-emitted JSON Schema stays
|
|
109
|
+
* sourceAccount, target network, optional RPC URL, optional base fee. Both
|
|
110
|
+
* share the same smart-account context, so the SDK-emitted JSON Schema stays
|
|
111
111
|
* identical for those fields. The body re-validates against the strict
|
|
112
112
|
* schemas in `@crediolabs/policy-synth/run`. */
|
|
113
113
|
const SmartAccountToolShape = {
|
|
114
114
|
smartAccount: z.string().min(1).optional(),
|
|
115
115
|
sourceAccount: z.string().min(1).optional(),
|
|
116
|
+
/** Omitting this from the shape made both tools testnet-ONLY. The input
|
|
117
|
+
* schema defaults `network` to `testnet` and expects a mainnet caller to
|
|
118
|
+
* set it, but a field absent from the tool shape is STRIPPED before the
|
|
119
|
+
* body runs, so an MCP client could not reach the mainnet pin at all. The
|
|
120
|
+
* failure presented as a deliberate testnet pin rather than as a missing
|
|
121
|
+
* parameter, which sent integrators to build the install by hand. */
|
|
122
|
+
network: NetworkSchema.optional(),
|
|
116
123
|
rpcUrl: z.string().url().optional(),
|
|
117
124
|
baseFee: z.number().int().positive().optional(),
|
|
118
125
|
}
|
|
119
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
|
+
|
|
120
143
|
/** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
|
|
121
144
|
* at the tool boundary because the rule schema is a discriminated union
|
|
122
145
|
* the SDK does not accept at registration; the body re-validates
|
|
@@ -126,6 +149,10 @@ const SmartAccountToolShape = {
|
|
|
126
149
|
* extracts them from there. */
|
|
127
150
|
export const InstallPolicyToolShape = {
|
|
128
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(),
|
|
129
156
|
rule: z.unknown().optional(),
|
|
130
157
|
installNonce: z.number().int().positive().optional(),
|
|
131
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
|
)
|