mrmainspring 0.1.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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/audit/service.d.ts +7 -0
- package/dist/audit/service.js +98 -0
- package/dist/audit/store.d.ts +7 -0
- package/dist/audit/store.js +37 -0
- package/dist/audit/supabase-store.d.ts +9 -0
- package/dist/audit/supabase-store.js +22 -0
- package/dist/audit/types.d.ts +31 -0
- package/dist/audit/types.js +1 -0
- package/dist/casper/anchorClient.d.ts +99 -0
- package/dist/casper/anchorClient.js +412 -0
- package/dist/config.d.ts +51 -0
- package/dist/config.js +215 -0
- package/dist/env-file.d.ts +1 -0
- package/dist/env-file.js +51 -0
- package/dist/grimoire/service.d.ts +13 -0
- package/dist/grimoire/service.js +199 -0
- package/dist/grimoire/store.d.ts +10 -0
- package/dist/grimoire/store.js +64 -0
- package/dist/grimoire/supabase-store.d.ts +13 -0
- package/dist/grimoire/supabase-store.js +50 -0
- package/dist/grimoire/types.d.ts +60 -0
- package/dist/grimoire/types.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +17 -0
- package/dist/mcp/auditTools.d.ts +3 -0
- package/dist/mcp/auditTools.js +13 -0
- package/dist/mcp/grimoireTools.d.ts +3 -0
- package/dist/mcp/grimoireTools.js +91 -0
- package/dist/mcp/jsonResult.d.ts +2 -0
- package/dist/mcp/jsonResult.js +10 -0
- package/dist/mcp/memoryTools.d.ts +3 -0
- package/dist/mcp/memoryTools.js +73 -0
- package/dist/mcp/paymentTools.d.ts +3 -0
- package/dist/mcp/paymentTools.js +33 -0
- package/dist/memory/canonical.d.ts +4 -0
- package/dist/memory/canonical.js +49 -0
- package/dist/memory/hash.d.ts +1 -0
- package/dist/memory/hash.js +4 -0
- package/dist/memory/service.d.ts +37 -0
- package/dist/memory/service.js +175 -0
- package/dist/memory/store.d.ts +8 -0
- package/dist/memory/store.js +49 -0
- package/dist/memory/supabase-store.d.ts +10 -0
- package/dist/memory/supabase-store.js +30 -0
- package/dist/memory/types.d.ts +56 -0
- package/dist/memory/types.js +7 -0
- package/dist/payments/service.d.ts +26 -0
- package/dist/payments/service.js +613 -0
- package/dist/payments/store.d.ts +10 -0
- package/dist/payments/store.js +64 -0
- package/dist/payments/supabase-store.d.ts +13 -0
- package/dist/payments/supabase-store.js +51 -0
- package/dist/payments/types.d.ts +101 -0
- package/dist/payments/types.js +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +68 -0
- package/dist/storage/json-file-store.d.ts +17 -0
- package/dist/storage/json-file-store.js +87 -0
- package/dist/storage/store-factory.d.ts +12 -0
- package/dist/storage/store-factory.js +26 -0
- package/dist/storage/supabase-rest.d.ts +26 -0
- package/dist/storage/supabase-rest.js +85 -0
- package/dist/x402/client.d.ts +44 -0
- package/dist/x402/client.js +95 -0
- package/dist/x402/facilitator.d.ts +84 -0
- package/dist/x402/facilitator.js +800 -0
- package/dist/x402/readiness.d.ts +55 -0
- package/dist/x402/readiness.js +433 -0
- package/dist/x402/redaction.d.ts +1 -0
- package/dist/x402/redaction.js +30 -0
- package/dist/x402/resource.d.ts +69 -0
- package/dist/x402/resource.js +325 -0
- package/dist/x402/settlement.d.ts +176 -0
- package/dist/x402/settlement.js +1210 -0
- package/dist/x402/signer.d.ts +71 -0
- package/dist/x402/signer.js +616 -0
- package/package.json +61 -0
package/dist/env-file.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
export function loadLocalEnvFile(env = process.env) {
|
|
5
|
+
const envPath = resolveEnvPath(env);
|
|
6
|
+
if (!envPath || !existsSync(envPath)) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const raw = readFileSync(envPath, "utf8");
|
|
10
|
+
for (const [key, value] of parseEnv(raw)) {
|
|
11
|
+
if (env[key] === undefined) {
|
|
12
|
+
env[key] = value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return envPath;
|
|
16
|
+
}
|
|
17
|
+
function resolveEnvPath(env) {
|
|
18
|
+
if (env.SIGIL_ENV_FILE?.trim()) {
|
|
19
|
+
return env.SIGIL_ENV_FILE.trim();
|
|
20
|
+
}
|
|
21
|
+
const backendRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
22
|
+
const repoRoot = dirname(backendRoot);
|
|
23
|
+
const candidates = [join(process.cwd(), ".env"), join(backendRoot, ".env"), join(repoRoot, ".env")];
|
|
24
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? candidates.at(-1) ?? null;
|
|
25
|
+
}
|
|
26
|
+
function parseEnv(raw) {
|
|
27
|
+
const entries = [];
|
|
28
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
29
|
+
const trimmed = line.trim();
|
|
30
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const separator = trimmed.indexOf("=");
|
|
34
|
+
if (separator <= 0) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const key = trimmed.slice(0, separator).trim();
|
|
38
|
+
const value = unquote(trimmed.slice(separator + 1).trim());
|
|
39
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
40
|
+
entries.push([key, value]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return entries;
|
|
44
|
+
}
|
|
45
|
+
function unquote(value) {
|
|
46
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
47
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
48
|
+
return value.slice(1, -1);
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AuditService } from "../audit/service.js";
|
|
2
|
+
import type { GrimoireStore, PolicyRecord, PolicySetInput, SecretMetadata, SecretPutInput } from "./types.js";
|
|
3
|
+
export declare class GrimoireService {
|
|
4
|
+
private readonly store;
|
|
5
|
+
private readonly masterKey;
|
|
6
|
+
private readonly audit?;
|
|
7
|
+
constructor(store: GrimoireStore, masterKey: Buffer, audit?: AuditService | undefined);
|
|
8
|
+
putSecret(input: SecretPutInput): Promise<SecretMetadata>;
|
|
9
|
+
listSecrets(agentId: string): Promise<SecretMetadata[]>;
|
|
10
|
+
setPolicy(input: PolicySetInput): Promise<PolicyRecord>;
|
|
11
|
+
getPolicy(agentId: string, policyId: string): Promise<PolicyRecord | null>;
|
|
12
|
+
recordPolicySpend(agentId: string, policyId: string, amount: string): Promise<PolicyRecord | null>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { createCipheriv, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
import { canonicalizeJson, toJsonObject } from "../memory/canonical.js";
|
|
3
|
+
import { sha256Hex } from "../memory/hash.js";
|
|
4
|
+
export class GrimoireService {
|
|
5
|
+
store;
|
|
6
|
+
masterKey;
|
|
7
|
+
audit;
|
|
8
|
+
constructor(store, masterKey, audit) {
|
|
9
|
+
this.store = store;
|
|
10
|
+
this.masterKey = masterKey;
|
|
11
|
+
this.audit = audit;
|
|
12
|
+
}
|
|
13
|
+
async putSecret(input) {
|
|
14
|
+
const now = new Date().toISOString();
|
|
15
|
+
const encrypted = encrypt(input.value, this.masterKey);
|
|
16
|
+
const existing = await this.store.getSecretByName(input.agent_id, input.name);
|
|
17
|
+
const record = {
|
|
18
|
+
id: existing?.id ?? createId("sec"),
|
|
19
|
+
agent_id: input.agent_id,
|
|
20
|
+
name: input.name,
|
|
21
|
+
type: input.type,
|
|
22
|
+
scopes: [...new Set(input.scopes)].sort(),
|
|
23
|
+
ciphertext: encrypted.ciphertext,
|
|
24
|
+
nonce: encrypted.nonce,
|
|
25
|
+
auth_tag: encrypted.authTag,
|
|
26
|
+
key_version: "local-v1",
|
|
27
|
+
created_at: existing?.created_at ?? now,
|
|
28
|
+
updated_at: now,
|
|
29
|
+
deleted_at: null
|
|
30
|
+
};
|
|
31
|
+
await this.store.saveSecret(record);
|
|
32
|
+
await this.audit?.record({
|
|
33
|
+
agent_id: record.agent_id,
|
|
34
|
+
event_type: "secret.stored",
|
|
35
|
+
subject_type: "secret",
|
|
36
|
+
subject_id: record.id,
|
|
37
|
+
metadata: {
|
|
38
|
+
name: record.name,
|
|
39
|
+
type: record.type,
|
|
40
|
+
scopes: record.scopes
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return toSecretMetadata(record);
|
|
44
|
+
}
|
|
45
|
+
async listSecrets(agentId) {
|
|
46
|
+
const secrets = await this.store.listSecrets(agentId);
|
|
47
|
+
const metadata = secrets.filter((secret) => !secret.deleted_at).map(toSecretMetadata);
|
|
48
|
+
await this.audit?.record({
|
|
49
|
+
agent_id: agentId,
|
|
50
|
+
event_type: "secret.listed",
|
|
51
|
+
subject_type: "secret",
|
|
52
|
+
metadata: { count: metadata.length }
|
|
53
|
+
});
|
|
54
|
+
return metadata;
|
|
55
|
+
}
|
|
56
|
+
async setPolicy(input) {
|
|
57
|
+
assertDecimalAmount("max_amount_per_call", input.max_amount_per_call);
|
|
58
|
+
assertDecimalAmount("max_amount_per_period", input.max_amount_per_period);
|
|
59
|
+
const now = new Date().toISOString();
|
|
60
|
+
const existing = await this.store.getPolicy(input.agent_id, input.policy_id);
|
|
61
|
+
const policyBody = {
|
|
62
|
+
agent_id: input.agent_id,
|
|
63
|
+
policy_id: input.policy_id,
|
|
64
|
+
enabled: input.enabled ?? true,
|
|
65
|
+
allowed_urls: input.allowed_urls,
|
|
66
|
+
allowed_methods: input.allowed_methods.map((method) => method.toUpperCase()).sort(),
|
|
67
|
+
allowed_asset: toJsonObject(input.allowed_asset, "allowed_asset"),
|
|
68
|
+
max_amount_per_call: input.max_amount_per_call,
|
|
69
|
+
max_amount_per_period: input.max_amount_per_period,
|
|
70
|
+
period_seconds: input.period_seconds,
|
|
71
|
+
secret_scopes: [...new Set(input.secret_scopes)].sort()
|
|
72
|
+
};
|
|
73
|
+
const record = {
|
|
74
|
+
...policyBody,
|
|
75
|
+
policy_hash: sha256Hex(canonicalizeJson(policyBody)),
|
|
76
|
+
current_period_spend: existing?.current_period_spend ?? "0",
|
|
77
|
+
period_started_at: existing?.period_started_at ?? now,
|
|
78
|
+
created_at: existing?.created_at ?? now,
|
|
79
|
+
updated_at: now
|
|
80
|
+
};
|
|
81
|
+
await this.store.savePolicy(record);
|
|
82
|
+
await this.audit?.record({
|
|
83
|
+
agent_id: record.agent_id,
|
|
84
|
+
event_type: "policy.set",
|
|
85
|
+
subject_type: "policy",
|
|
86
|
+
subject_id: record.policy_id,
|
|
87
|
+
metadata: {
|
|
88
|
+
enabled: record.enabled,
|
|
89
|
+
policy_hash: record.policy_hash,
|
|
90
|
+
allowed_urls: record.allowed_urls,
|
|
91
|
+
allowed_methods: record.allowed_methods
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return record;
|
|
95
|
+
}
|
|
96
|
+
async getPolicy(agentId, policyId) {
|
|
97
|
+
const policy = await this.store.getPolicy(agentId, policyId);
|
|
98
|
+
await this.audit?.record({
|
|
99
|
+
agent_id: agentId,
|
|
100
|
+
event_type: "policy.get",
|
|
101
|
+
subject_type: "policy",
|
|
102
|
+
subject_id: policyId,
|
|
103
|
+
metadata: { found: Boolean(policy) }
|
|
104
|
+
});
|
|
105
|
+
return policy;
|
|
106
|
+
}
|
|
107
|
+
async recordPolicySpend(agentId, policyId, amount) {
|
|
108
|
+
assertDecimalAmount("amount", amount);
|
|
109
|
+
const policy = await this.store.getPolicy(agentId, policyId);
|
|
110
|
+
if (!policy) {
|
|
111
|
+
await this.audit?.record({
|
|
112
|
+
agent_id: agentId,
|
|
113
|
+
event_type: "policy.spend_record_failed",
|
|
114
|
+
subject_type: "policy",
|
|
115
|
+
subject_id: policyId,
|
|
116
|
+
severity: "warn",
|
|
117
|
+
metadata: { reason: "policy_not_found" }
|
|
118
|
+
});
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const now = new Date();
|
|
122
|
+
const nowIso = now.toISOString();
|
|
123
|
+
const periodStartedAt = Date.parse(policy.period_started_at);
|
|
124
|
+
const periodExpired = Number.isNaN(periodStartedAt) ||
|
|
125
|
+
now.getTime() - periodStartedAt >= policy.period_seconds * 1000;
|
|
126
|
+
const currentSpend = periodExpired ? "0" : policy.current_period_spend;
|
|
127
|
+
const updated = {
|
|
128
|
+
...policy,
|
|
129
|
+
current_period_spend: addDecimalAmounts(currentSpend, amount),
|
|
130
|
+
period_started_at: periodExpired ? nowIso : policy.period_started_at,
|
|
131
|
+
updated_at: nowIso
|
|
132
|
+
};
|
|
133
|
+
await this.store.savePolicy(updated);
|
|
134
|
+
await this.audit?.record({
|
|
135
|
+
agent_id: agentId,
|
|
136
|
+
event_type: "policy.spend_recorded",
|
|
137
|
+
subject_type: "policy",
|
|
138
|
+
subject_id: policyId,
|
|
139
|
+
metadata: {
|
|
140
|
+
amount,
|
|
141
|
+
current_period_spend: updated.current_period_spend,
|
|
142
|
+
policy_hash: updated.policy_hash
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
return updated;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function encrypt(value, key) {
|
|
149
|
+
const nonce = randomBytes(12);
|
|
150
|
+
const cipher = createCipheriv("aes-256-gcm", key, nonce);
|
|
151
|
+
const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
152
|
+
const authTag = cipher.getAuthTag();
|
|
153
|
+
return {
|
|
154
|
+
ciphertext: ciphertext.toString("base64"),
|
|
155
|
+
nonce: nonce.toString("base64"),
|
|
156
|
+
authTag: authTag.toString("base64")
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function toSecretMetadata(secret) {
|
|
160
|
+
return {
|
|
161
|
+
id: secret.id,
|
|
162
|
+
agent_id: secret.agent_id,
|
|
163
|
+
name: secret.name,
|
|
164
|
+
type: secret.type,
|
|
165
|
+
scopes: secret.scopes,
|
|
166
|
+
created_at: secret.created_at,
|
|
167
|
+
updated_at: secret.updated_at
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function createId(prefix) {
|
|
171
|
+
return `${prefix}_${randomUUID().replaceAll("-", "")}`;
|
|
172
|
+
}
|
|
173
|
+
function assertDecimalAmount(name, value) {
|
|
174
|
+
if (!/^\d+(\.\d+)?$/.test(value)) {
|
|
175
|
+
throw new Error(`${name} must be a non-negative decimal amount`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function addDecimalAmounts(left, right) {
|
|
179
|
+
const leftParts = parseDecimalAmount(left);
|
|
180
|
+
const rightParts = parseDecimalAmount(right);
|
|
181
|
+
const scale = Math.max(leftParts.scale, rightParts.scale);
|
|
182
|
+
const leftValue = leftParts.value * 10n ** BigInt(scale - leftParts.scale);
|
|
183
|
+
const rightValue = rightParts.value * 10n ** BigInt(scale - rightParts.scale);
|
|
184
|
+
const sum = (leftValue + rightValue).toString().padStart(scale + 1, "0");
|
|
185
|
+
if (scale === 0) {
|
|
186
|
+
return sum;
|
|
187
|
+
}
|
|
188
|
+
const whole = sum.slice(0, -scale);
|
|
189
|
+
const fraction = sum.slice(-scale).replace(/0+$/, "");
|
|
190
|
+
return fraction ? `${whole}.${fraction}` : whole;
|
|
191
|
+
}
|
|
192
|
+
function parseDecimalAmount(value) {
|
|
193
|
+
assertDecimalAmount("amount", value);
|
|
194
|
+
const [whole, fraction = ""] = value.split(".");
|
|
195
|
+
return {
|
|
196
|
+
value: BigInt(`${whole}${fraction}`),
|
|
197
|
+
scale: fraction.length
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GrimoireStore, PolicyRecord, SecretRecord } from "./types.js";
|
|
2
|
+
export declare class FileGrimoireStore implements GrimoireStore {
|
|
3
|
+
private readonly store;
|
|
4
|
+
constructor(dataDir: string);
|
|
5
|
+
saveSecret(secret: SecretRecord): Promise<void>;
|
|
6
|
+
getSecretByName(agentId: string, name: string): Promise<SecretRecord | null>;
|
|
7
|
+
listSecrets(agentId: string): Promise<SecretRecord[]>;
|
|
8
|
+
savePolicy(policy: PolicyRecord): Promise<void>;
|
|
9
|
+
getPolicy(agentId: string, policyId: string): Promise<PolicyRecord | null>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { JsonFileStore } from "../storage/json-file-store.js";
|
|
3
|
+
export class FileGrimoireStore {
|
|
4
|
+
store;
|
|
5
|
+
constructor(dataDir) {
|
|
6
|
+
this.store = new JsonFileStore({
|
|
7
|
+
filePath: join(dataDir, "grimoire.json"),
|
|
8
|
+
empty: emptyStore,
|
|
9
|
+
normalize: normalizeStore
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
async saveSecret(secret) {
|
|
13
|
+
await this.store.update((data) => {
|
|
14
|
+
const existingIndex = data.secrets.findIndex((item) => item.id === secret.id);
|
|
15
|
+
if (existingIndex >= 0) {
|
|
16
|
+
data.secrets[existingIndex] = secret;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
data.secrets.push(secret);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async getSecretByName(agentId, name) {
|
|
24
|
+
const data = await this.store.read();
|
|
25
|
+
return (data.secrets.find((secret) => secret.agent_id === agentId && secret.name === name && !secret.deleted_at) ?? null);
|
|
26
|
+
}
|
|
27
|
+
async listSecrets(agentId) {
|
|
28
|
+
const data = await this.store.read();
|
|
29
|
+
return data.secrets.filter((secret) => secret.agent_id === agentId);
|
|
30
|
+
}
|
|
31
|
+
async savePolicy(policy) {
|
|
32
|
+
await this.store.update((data) => {
|
|
33
|
+
const existingIndex = data.policies.findIndex((item) => item.agent_id === policy.agent_id && item.policy_id === policy.policy_id);
|
|
34
|
+
if (existingIndex >= 0) {
|
|
35
|
+
data.policies[existingIndex] = policy;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
data.policies.push(policy);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async getPolicy(agentId, policyId) {
|
|
43
|
+
const data = await this.store.read();
|
|
44
|
+
return (data.policies.find((policy) => policy.agent_id === agentId && policy.policy_id === policyId) ?? null);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function emptyStore() {
|
|
48
|
+
return {
|
|
49
|
+
schema_version: "sigil.grimoire-store.v1",
|
|
50
|
+
secrets: [],
|
|
51
|
+
policies: []
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function normalizeStore(parsed) {
|
|
55
|
+
const data = asStoreObject(parsed);
|
|
56
|
+
return {
|
|
57
|
+
schema_version: "sigil.grimoire-store.v1",
|
|
58
|
+
secrets: Array.isArray(data.secrets) ? data.secrets : [],
|
|
59
|
+
policies: Array.isArray(data.policies) ? data.policies : []
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function asStoreObject(parsed) {
|
|
63
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
64
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SupabaseRestClient } from "../storage/supabase-rest.js";
|
|
2
|
+
import type { GrimoireStore, PolicyRecord, SecretRecord } from "./types.js";
|
|
3
|
+
export declare class SupabaseGrimoireStore implements GrimoireStore {
|
|
4
|
+
private readonly client;
|
|
5
|
+
private readonly secretsTable;
|
|
6
|
+
private readonly policiesTable;
|
|
7
|
+
constructor(client: SupabaseRestClient);
|
|
8
|
+
saveSecret(secret: SecretRecord): Promise<void>;
|
|
9
|
+
getSecretByName(agentId: string, name: string): Promise<SecretRecord | null>;
|
|
10
|
+
listSecrets(agentId: string): Promise<SecretRecord[]>;
|
|
11
|
+
savePolicy(policy: PolicyRecord): Promise<void>;
|
|
12
|
+
getPolicy(agentId: string, policyId: string): Promise<PolicyRecord | null>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export class SupabaseGrimoireStore {
|
|
2
|
+
client;
|
|
3
|
+
secretsTable;
|
|
4
|
+
policiesTable;
|
|
5
|
+
constructor(client) {
|
|
6
|
+
this.client = client;
|
|
7
|
+
this.secretsTable = client.table("secrets");
|
|
8
|
+
this.policiesTable = client.table("policies");
|
|
9
|
+
}
|
|
10
|
+
async saveSecret(secret) {
|
|
11
|
+
await this.client.upsert(this.secretsTable, {
|
|
12
|
+
id: secret.id,
|
|
13
|
+
agent_id: secret.agent_id,
|
|
14
|
+
name: secret.name,
|
|
15
|
+
created_at: secret.created_at,
|
|
16
|
+
updated_at: secret.updated_at,
|
|
17
|
+
deleted_at: secret.deleted_at,
|
|
18
|
+
record: secret
|
|
19
|
+
}, "id");
|
|
20
|
+
}
|
|
21
|
+
async getSecretByName(agentId, name) {
|
|
22
|
+
const records = await this.client.selectRecords(this.secretsTable, {
|
|
23
|
+
filters: { agent_id: agentId, name },
|
|
24
|
+
order: { column: "created_at" }
|
|
25
|
+
});
|
|
26
|
+
return records.find((secret) => !secret.deleted_at) ?? null;
|
|
27
|
+
}
|
|
28
|
+
async listSecrets(agentId) {
|
|
29
|
+
return this.client.selectRecords(this.secretsTable, {
|
|
30
|
+
filters: { agent_id: agentId },
|
|
31
|
+
order: { column: "created_at" }
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async savePolicy(policy) {
|
|
35
|
+
await this.client.upsert(this.policiesTable, {
|
|
36
|
+
agent_id: policy.agent_id,
|
|
37
|
+
policy_id: policy.policy_id,
|
|
38
|
+
created_at: policy.created_at,
|
|
39
|
+
updated_at: policy.updated_at,
|
|
40
|
+
record: policy
|
|
41
|
+
}, "agent_id,policy_id");
|
|
42
|
+
}
|
|
43
|
+
async getPolicy(agentId, policyId) {
|
|
44
|
+
const [record] = await this.client.selectRecords(this.policiesTable, {
|
|
45
|
+
filters: { agent_id: agentId, policy_id: policyId },
|
|
46
|
+
limit: 1
|
|
47
|
+
});
|
|
48
|
+
return record ?? null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { JsonObject } from "../memory/types.js";
|
|
2
|
+
export type SecretType = "casper_private_key_ref" | "x402_client_key_ref" | "api_key" | "webhook_secret";
|
|
3
|
+
export type SecretPutInput = {
|
|
4
|
+
agent_id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
type: SecretType;
|
|
7
|
+
value: string;
|
|
8
|
+
scopes: string[];
|
|
9
|
+
};
|
|
10
|
+
export type SecretRecord = {
|
|
11
|
+
id: string;
|
|
12
|
+
agent_id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
type: SecretType;
|
|
15
|
+
scopes: string[];
|
|
16
|
+
ciphertext: string;
|
|
17
|
+
nonce: string;
|
|
18
|
+
auth_tag: string;
|
|
19
|
+
key_version: string;
|
|
20
|
+
created_at: string;
|
|
21
|
+
updated_at: string;
|
|
22
|
+
deleted_at: string | null;
|
|
23
|
+
};
|
|
24
|
+
export type SecretMetadata = Pick<SecretRecord, "id" | "agent_id" | "name" | "type" | "scopes" | "created_at" | "updated_at">;
|
|
25
|
+
export type PolicySetInput = {
|
|
26
|
+
agent_id: string;
|
|
27
|
+
policy_id: string;
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
allowed_urls: string[];
|
|
30
|
+
allowed_methods: string[];
|
|
31
|
+
allowed_asset: unknown;
|
|
32
|
+
max_amount_per_call: string;
|
|
33
|
+
max_amount_per_period: string;
|
|
34
|
+
period_seconds: number;
|
|
35
|
+
secret_scopes: string[];
|
|
36
|
+
};
|
|
37
|
+
export type PolicyRecord = {
|
|
38
|
+
agent_id: string;
|
|
39
|
+
policy_id: string;
|
|
40
|
+
enabled: boolean;
|
|
41
|
+
allowed_urls: string[];
|
|
42
|
+
allowed_methods: string[];
|
|
43
|
+
allowed_asset: JsonObject;
|
|
44
|
+
max_amount_per_call: string;
|
|
45
|
+
max_amount_per_period: string;
|
|
46
|
+
period_seconds: number;
|
|
47
|
+
secret_scopes: string[];
|
|
48
|
+
policy_hash: string;
|
|
49
|
+
current_period_spend: string;
|
|
50
|
+
period_started_at: string;
|
|
51
|
+
created_at: string;
|
|
52
|
+
updated_at: string;
|
|
53
|
+
};
|
|
54
|
+
export type GrimoireStore = {
|
|
55
|
+
saveSecret(secret: SecretRecord): Promise<void>;
|
|
56
|
+
getSecretByName(agentId: string, name: string): Promise<SecretRecord | null>;
|
|
57
|
+
listSecrets(agentId: string): Promise<SecretRecord[]>;
|
|
58
|
+
savePolicy(policy: PolicyRecord): Promise<void>;
|
|
59
|
+
getPolicy(agentId: string, policyId: string): Promise<PolicyRecord | null>;
|
|
60
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { loadConfig } from "./config.js";
|
|
4
|
+
import { loadLocalEnvFile } from "./env-file.js";
|
|
5
|
+
import { createSigilServer } from "./server.js";
|
|
6
|
+
async function main() {
|
|
7
|
+
loadLocalEnvFile();
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
const server = createSigilServer(config);
|
|
10
|
+
const transport = new StdioServerTransport();
|
|
11
|
+
await server.connect(transport);
|
|
12
|
+
}
|
|
13
|
+
main().catch((error) => {
|
|
14
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
15
|
+
console.error(message);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonResult } from "./jsonResult.js";
|
|
3
|
+
export function registerAuditTools(server, auditService) {
|
|
4
|
+
server.registerTool("audit.tail", {
|
|
5
|
+
title: "Tail Audit Events",
|
|
6
|
+
description: "Return recent Mr Mainspring audit events for memory, Grimoire, payment, and anchor activity.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
agent_id: z.string().min(1).optional(),
|
|
9
|
+
event_type: z.string().min(1).optional(),
|
|
10
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
11
|
+
}
|
|
12
|
+
}, async (input) => jsonResult(await auditService.tail(input)));
|
|
13
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonResult } from "./jsonResult.js";
|
|
3
|
+
const secretTypeSchema = z.enum([
|
|
4
|
+
"casper_private_key_ref",
|
|
5
|
+
"x402_client_key_ref",
|
|
6
|
+
"api_key",
|
|
7
|
+
"webhook_secret"
|
|
8
|
+
]);
|
|
9
|
+
const jsonObjectSchema = z.record(z.string(), z.unknown());
|
|
10
|
+
const nonEmptyStringSchema = z.string().trim().min(1);
|
|
11
|
+
const urlSchema = z.string().trim().url();
|
|
12
|
+
const decimalAmountSchema = z
|
|
13
|
+
.string()
|
|
14
|
+
.trim()
|
|
15
|
+
.regex(/^\d+(\.\d+)?$/, "Expected a non-negative decimal amount");
|
|
16
|
+
export function registerGrimoireTools(server, grimoireService) {
|
|
17
|
+
server.registerTool("grimoire.secret.put", {
|
|
18
|
+
title: "Store Secret",
|
|
19
|
+
description: "Encrypt and store a scoped Mr Mainspring secret. The plaintext is never returned.",
|
|
20
|
+
inputSchema: {
|
|
21
|
+
agent_id: nonEmptyStringSchema,
|
|
22
|
+
name: nonEmptyStringSchema,
|
|
23
|
+
type: secretTypeSchema,
|
|
24
|
+
value: nonEmptyStringSchema,
|
|
25
|
+
scopes: z.array(nonEmptyStringSchema).min(1)
|
|
26
|
+
}
|
|
27
|
+
}, async (input) => {
|
|
28
|
+
const secret = await grimoireService.putSecret(input);
|
|
29
|
+
return jsonResult({
|
|
30
|
+
status: "stored",
|
|
31
|
+
secret
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
server.registerTool("grimoire.secret.list", {
|
|
35
|
+
title: "List Secrets",
|
|
36
|
+
description: "List Mr Mainspring secret metadata for an agent without exposing secret values.",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
agent_id: nonEmptyStringSchema
|
|
39
|
+
}
|
|
40
|
+
}, async ({ agent_id }) => {
|
|
41
|
+
const secrets = await grimoireService.listSecrets(agent_id);
|
|
42
|
+
return jsonResult({
|
|
43
|
+
agent_id,
|
|
44
|
+
count: secrets.length,
|
|
45
|
+
secrets
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
server.registerTool("grimoire.policy.set", {
|
|
49
|
+
title: "Set Policy",
|
|
50
|
+
description: "Create or update a Mr Mainspring spending/access policy commitment.",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
agent_id: nonEmptyStringSchema,
|
|
53
|
+
policy_id: nonEmptyStringSchema,
|
|
54
|
+
enabled: z.boolean().optional(),
|
|
55
|
+
allowed_urls: z.array(urlSchema).min(1),
|
|
56
|
+
allowed_methods: z.array(nonEmptyStringSchema).min(1),
|
|
57
|
+
allowed_asset: jsonObjectSchema,
|
|
58
|
+
max_amount_per_call: decimalAmountSchema,
|
|
59
|
+
max_amount_per_period: decimalAmountSchema,
|
|
60
|
+
period_seconds: z.number().int().positive(),
|
|
61
|
+
secret_scopes: z.array(nonEmptyStringSchema).default([])
|
|
62
|
+
}
|
|
63
|
+
}, async (input) => {
|
|
64
|
+
const policy = await grimoireService.setPolicy(input);
|
|
65
|
+
return jsonResult({
|
|
66
|
+
status: "stored",
|
|
67
|
+
policy
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
server.registerTool("grimoire.policy.get", {
|
|
71
|
+
title: "Get Policy",
|
|
72
|
+
description: "Read one Mr Mainspring policy and current local spend metadata.",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
agent_id: nonEmptyStringSchema,
|
|
75
|
+
policy_id: nonEmptyStringSchema
|
|
76
|
+
}
|
|
77
|
+
}, async ({ agent_id, policy_id }) => {
|
|
78
|
+
const policy = await grimoireService.getPolicy(agent_id, policy_id);
|
|
79
|
+
if (!policy) {
|
|
80
|
+
return jsonResult({
|
|
81
|
+
found: false,
|
|
82
|
+
agent_id,
|
|
83
|
+
policy_id
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return jsonResult({
|
|
87
|
+
found: true,
|
|
88
|
+
policy
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|