@crediolabs/policy-builder-mcp 0.3.1 → 0.5.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 +35 -0
- package/dist/src/schemas.js +27 -0
- package/dist/src/server.js +4 -3
- package/dist-cjs/src/schemas.d.ts +35 -0
- package/dist-cjs/src/schemas.js +28 -1
- package/dist-cjs/src/server.js +2 -1
- package/package.json +2 -2
- package/src/schemas.ts +28 -0
- package/src/server.ts +10 -1
package/dist/src/schemas.d.ts
CHANGED
|
@@ -834,6 +834,37 @@ 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
|
+
readonly minOutputRatio: z.ZodOptional<z.ZodObject<{
|
|
852
|
+
num: z.ZodString;
|
|
853
|
+
den: z.ZodString;
|
|
854
|
+
inputArgIndex: z.ZodNumber;
|
|
855
|
+
outputArgIndex: z.ZodNumber;
|
|
856
|
+
}, "strip", z.ZodTypeAny, {
|
|
857
|
+
num: string;
|
|
858
|
+
den: string;
|
|
859
|
+
inputArgIndex: number;
|
|
860
|
+
outputArgIndex: number;
|
|
861
|
+
}, {
|
|
862
|
+
num: string;
|
|
863
|
+
den: string;
|
|
864
|
+
inputArgIndex: number;
|
|
865
|
+
outputArgIndex: number;
|
|
866
|
+
}>>;
|
|
867
|
+
};
|
|
837
868
|
/** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
|
|
838
869
|
* at the tool boundary because the rule schema is a discriminated union
|
|
839
870
|
* the SDK does not accept at registration; the body re-validates
|
|
@@ -842,6 +873,10 @@ export type { GetInterpreterInfoInput, InstallPolicyInput, RevokePolicyInput, Si
|
|
|
842
873
|
* (the policy already carries the encoded predicate); the run-layer
|
|
843
874
|
* extracts them from there. */
|
|
844
875
|
export declare const InstallPolicyToolShape: {
|
|
876
|
+
/** Rules already on the account. Supplying them turns on the cross-rule
|
|
877
|
+
* authority scan; omitting them returns `authorityScan: null`, which means
|
|
878
|
+
* "not checked" rather than "nothing found". */
|
|
879
|
+
readonly existingRules: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
|
|
845
880
|
readonly rule: z.ZodOptional<z.ZodUnknown>;
|
|
846
881
|
readonly installNonce: z.ZodOptional<z.ZodNumber>;
|
|
847
882
|
readonly interpreterAddress: z.ZodOptional<z.ZodString>;
|
package/dist/src/schemas.js
CHANGED
|
@@ -84,6 +84,29 @@ 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
|
+
minOutputRatio: z
|
|
102
|
+
.object({
|
|
103
|
+
num: z.string(),
|
|
104
|
+
den: z.string(),
|
|
105
|
+
inputArgIndex: z.number().int().nonnegative(),
|
|
106
|
+
outputArgIndex: z.number().int().nonnegative(),
|
|
107
|
+
})
|
|
108
|
+
.optional(),
|
|
109
|
+
};
|
|
87
110
|
/** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
|
|
88
111
|
* at the tool boundary because the rule schema is a discriminated union
|
|
89
112
|
* the SDK does not accept at registration; the body re-validates
|
|
@@ -93,6 +116,10 @@ const SmartAccountToolShape = {
|
|
|
93
116
|
* extracts them from there. */
|
|
94
117
|
export const InstallPolicyToolShape = {
|
|
95
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: z.array(z.unknown()).optional(),
|
|
96
123
|
rule: z.unknown().optional(),
|
|
97
124
|
installNonce: z.number().int().positive().optional(),
|
|
98
125
|
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, a recipient allowlist, and a minimum-output ratio (a swap slippage floor, bounding the output argument against the call's own input). 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 4, and both belong to the OpenZeppelin account layer.", 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. The response carries 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. The account is READ over RPC to build it; pass `existingRules` to supply them yourself instead (useful offline). `authorityScan: null` means NOT CHECKED - the read failed, or it could not account for every live rule - and must never be read as "nothing found". 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,37 @@ 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
|
+
readonly minOutputRatio: z.ZodOptional<z.ZodObject<{
|
|
852
|
+
num: z.ZodString;
|
|
853
|
+
den: z.ZodString;
|
|
854
|
+
inputArgIndex: z.ZodNumber;
|
|
855
|
+
outputArgIndex: z.ZodNumber;
|
|
856
|
+
}, "strip", z.ZodTypeAny, {
|
|
857
|
+
num: string;
|
|
858
|
+
den: string;
|
|
859
|
+
inputArgIndex: number;
|
|
860
|
+
outputArgIndex: number;
|
|
861
|
+
}, {
|
|
862
|
+
num: string;
|
|
863
|
+
den: string;
|
|
864
|
+
inputArgIndex: number;
|
|
865
|
+
outputArgIndex: number;
|
|
866
|
+
}>>;
|
|
867
|
+
};
|
|
837
868
|
/** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
|
|
838
869
|
* at the tool boundary because the rule schema is a discriminated union
|
|
839
870
|
* the SDK does not accept at registration; the body re-validates
|
|
@@ -842,6 +873,10 @@ export type { GetInterpreterInfoInput, InstallPolicyInput, RevokePolicyInput, Si
|
|
|
842
873
|
* (the policy already carries the encoded predicate); the run-layer
|
|
843
874
|
* extracts them from there. */
|
|
844
875
|
export declare const InstallPolicyToolShape: {
|
|
876
|
+
/** Rules already on the account. Supplying them turns on the cross-rule
|
|
877
|
+
* authority scan; omitting them returns `authorityScan: null`, which means
|
|
878
|
+
* "not checked" rather than "nothing found". */
|
|
879
|
+
readonly existingRules: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
|
|
845
880
|
readonly rule: z.ZodOptional<z.ZodUnknown>;
|
|
846
881
|
readonly installNonce: z.ZodOptional<z.ZodNumber>;
|
|
847
882
|
readonly interpreterAddress: z.ZodOptional<z.ZodString>;
|
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; } });
|
|
@@ -92,6 +92,29 @@ 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
|
+
minOutputRatio: zod_1.z
|
|
110
|
+
.object({
|
|
111
|
+
num: zod_1.z.string(),
|
|
112
|
+
den: zod_1.z.string(),
|
|
113
|
+
inputArgIndex: zod_1.z.number().int().nonnegative(),
|
|
114
|
+
outputArgIndex: zod_1.z.number().int().nonnegative(),
|
|
115
|
+
})
|
|
116
|
+
.optional(),
|
|
117
|
+
};
|
|
95
118
|
/** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
|
|
96
119
|
* at the tool boundary because the rule schema is a discriminated union
|
|
97
120
|
* the SDK does not accept at registration; the body re-validates
|
|
@@ -101,6 +124,10 @@ const SmartAccountToolShape = {
|
|
|
101
124
|
* extracts them from there. */
|
|
102
125
|
exports.InstallPolicyToolShape = {
|
|
103
126
|
...SmartAccountToolShape,
|
|
127
|
+
/** Rules already on the account. Supplying them turns on the cross-rule
|
|
128
|
+
* authority scan; omitting them returns `authorityScan: null`, which means
|
|
129
|
+
* "not checked" rather than "nothing found". */
|
|
130
|
+
existingRules: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
104
131
|
rule: zod_1.z.unknown().optional(),
|
|
105
132
|
installNonce: zod_1.z.number().int().positive().optional(),
|
|
106
133
|
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, a recipient allowlist, and a minimum-output ratio (a swap slippage floor, bounding the output argument against the call's own input). 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 4, and both belong to the OpenZeppelin account layer.", 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. The response carries 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. The account is READ over RPC to build it; pass `existingRules` to supply them yourself instead (useful offline). `authorityScan: null` means NOT CHECKED - the read failed, or it could not account for every live rule - and must never be read as "nothing found". 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.5.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.
|
|
66
|
+
"@crediolabs/policy-synth": "0.5.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,30 @@ 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
|
+
minOutputRatio: z
|
|
142
|
+
.object({
|
|
143
|
+
num: z.string(),
|
|
144
|
+
den: z.string(),
|
|
145
|
+
inputArgIndex: z.number().int().nonnegative(),
|
|
146
|
+
outputArgIndex: z.number().int().nonnegative(),
|
|
147
|
+
})
|
|
148
|
+
.optional(),
|
|
149
|
+
} as const
|
|
150
|
+
|
|
127
151
|
/** Flat ZodRawShape for `install_policy`. `rule` is typed as `z.unknown()`
|
|
128
152
|
* at the tool boundary because the rule schema is a discriminated union
|
|
129
153
|
* the SDK does not accept at registration; the body re-validates
|
|
@@ -133,6 +157,10 @@ const SmartAccountToolShape = {
|
|
|
133
157
|
* extracts them from there. */
|
|
134
158
|
export const InstallPolicyToolShape = {
|
|
135
159
|
...SmartAccountToolShape,
|
|
160
|
+
/** Rules already on the account. Supplying them turns on the cross-rule
|
|
161
|
+
* authority scan; omitting them returns `authorityScan: null`, which means
|
|
162
|
+
* "not checked" rather than "nothing found". */
|
|
163
|
+
existingRules: z.array(z.unknown()).optional(),
|
|
136
164
|
rule: z.unknown().optional(),
|
|
137
165
|
installNonce: z.number().int().positive().optional(),
|
|
138
166
|
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, a recipient allowlist, and a minimum-output ratio (a swap slippage floor, bounding the output argument against the call's own input). 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 4, and both belong to the OpenZeppelin account layer.",
|
|
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. The response carries 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. The account is READ over RPC to build it; pass `existingRules` to supply them yourself instead (useful offline). `authorityScan: null` means NOT CHECKED - the read failed, or it could not account for every live rule - and must never be read as "nothing found". 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
|
)
|