@nobulex/mcp 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/dist/index.js +364 -0
- package/dist/index.mjs +338 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
PRESETS: () => PRESETS,
|
|
24
|
+
SteleGuard: () => SteleGuard
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
var import_crypto = require("@nobulex/crypto");
|
|
28
|
+
var import_ccl = require("@nobulex/ccl");
|
|
29
|
+
var import_core = require("@nobulex/core");
|
|
30
|
+
var import_enforcement = require("@nobulex/enforcement");
|
|
31
|
+
var import_identity = require("@nobulex/identity");
|
|
32
|
+
var import_reputation = require("@nobulex/reputation");
|
|
33
|
+
var import_proof = require("@nobulex/proof");
|
|
34
|
+
|
|
35
|
+
// src/presets.ts
|
|
36
|
+
var PRESETS = {
|
|
37
|
+
"standard:data-isolation": `permit file.read on '/data/**'
|
|
38
|
+
permit tool.read_file on '/data/**'
|
|
39
|
+
permit tool.* on '/data/**'
|
|
40
|
+
deny file.write on '**' severity high
|
|
41
|
+
deny network.send on '**' severity critical
|
|
42
|
+
deny network.send on '**' when payload.contains_pii = true severity critical
|
|
43
|
+
require audit.log on '**' severity critical
|
|
44
|
+
limit api.call 100 per 60 seconds`,
|
|
45
|
+
"standard:read-write": `permit file.read on '/data/**'
|
|
46
|
+
permit file.write on '/output/**'
|
|
47
|
+
deny file.write on '/system/**' severity critical
|
|
48
|
+
deny network.send on '**' severity high
|
|
49
|
+
deny network.send on '**' when payload.contains_pii = true severity critical
|
|
50
|
+
require audit.log on '**' severity critical`,
|
|
51
|
+
"standard:network": `permit file.read on '/data/**'
|
|
52
|
+
permit file.write on '/output/**'
|
|
53
|
+
permit network.send on '**'
|
|
54
|
+
deny network.send on '**' when payload.contains_pii = true severity critical
|
|
55
|
+
deny file.write on '/system/**' severity critical
|
|
56
|
+
require audit.log on '**' severity critical
|
|
57
|
+
require encrypt.output on '**' when output.classification = 'sensitive' severity high
|
|
58
|
+
limit api.call 500 per 3600 seconds
|
|
59
|
+
limit network.send 100 per 60 seconds`,
|
|
60
|
+
"standard:minimal": `deny file.read on '**' severity high
|
|
61
|
+
deny file.write on '**' severity critical
|
|
62
|
+
deny network.send on '**' severity critical
|
|
63
|
+
require audit.log on '**' severity critical`
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/index.ts
|
|
67
|
+
function resolveConstraints(constraints) {
|
|
68
|
+
const trimmed = constraints.trim();
|
|
69
|
+
if (trimmed in PRESETS) {
|
|
70
|
+
return PRESETS[trimmed];
|
|
71
|
+
}
|
|
72
|
+
return trimmed;
|
|
73
|
+
}
|
|
74
|
+
function toolAction(toolName) {
|
|
75
|
+
return `tool.${toolName}`;
|
|
76
|
+
}
|
|
77
|
+
function toolResource(toolName, args) {
|
|
78
|
+
for (const key of ["path", "file", "url", "uri", "resource", "target", "name"]) {
|
|
79
|
+
const val = args[key];
|
|
80
|
+
if (typeof val === "string" && val.length > 0) {
|
|
81
|
+
return val;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return `/tool/${toolName}`;
|
|
85
|
+
}
|
|
86
|
+
function extractSeverity(matchedRule) {
|
|
87
|
+
if (matchedRule && typeof matchedRule === "object" && "severity" in matchedRule && typeof matchedRule.severity === "string") {
|
|
88
|
+
return matchedRule.severity;
|
|
89
|
+
}
|
|
90
|
+
return "medium";
|
|
91
|
+
}
|
|
92
|
+
function extractConstraint(matchedRule) {
|
|
93
|
+
if (matchedRule && typeof matchedRule === "object" && "type" in matchedRule) {
|
|
94
|
+
const rule = matchedRule;
|
|
95
|
+
const type = rule.type;
|
|
96
|
+
const action = rule.action ?? "*";
|
|
97
|
+
const resource = rule.resource ?? "*";
|
|
98
|
+
return `${type} ${action} on '${resource}'`;
|
|
99
|
+
}
|
|
100
|
+
return "default deny (no matching permit rule)";
|
|
101
|
+
}
|
|
102
|
+
var SteleGuard = class _SteleGuard {
|
|
103
|
+
// Private constructor - use static factory methods
|
|
104
|
+
constructor() {
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Wrap an MCP server with Stele accountability using constraint text
|
|
108
|
+
* (either a preset name or raw CCL).
|
|
109
|
+
*
|
|
110
|
+
* Generates a keypair if one is not provided, creates an agent identity,
|
|
111
|
+
* builds a covenant document, and returns a wrapped server that intercepts
|
|
112
|
+
* every tool call through the constraint monitor.
|
|
113
|
+
*/
|
|
114
|
+
static async wrap(server, options) {
|
|
115
|
+
const constraintSource = resolveConstraints(options.constraints);
|
|
116
|
+
(0, import_ccl.parse)(constraintSource);
|
|
117
|
+
const operatorKeyPair = options.operatorKeyPair ?? await (0, import_crypto.generateKeyPair)();
|
|
118
|
+
const model = options.model ?? {
|
|
119
|
+
provider: "unknown",
|
|
120
|
+
modelId: "unknown",
|
|
121
|
+
attestationType: "self_reported"
|
|
122
|
+
};
|
|
123
|
+
const identity = await (0, import_identity.createIdentity)({
|
|
124
|
+
operatorKeyPair,
|
|
125
|
+
operatorIdentifier: options.agentIdentifier,
|
|
126
|
+
model,
|
|
127
|
+
capabilities: (server.tools ?? []).map((t) => toolAction(t.name)),
|
|
128
|
+
deployment: {
|
|
129
|
+
runtime: "process"
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
const covenant = await (0, import_core.buildCovenant)({
|
|
133
|
+
issuer: {
|
|
134
|
+
id: options.agentIdentifier ?? operatorKeyPair.publicKeyHex,
|
|
135
|
+
publicKey: operatorKeyPair.publicKeyHex,
|
|
136
|
+
role: "issuer"
|
|
137
|
+
},
|
|
138
|
+
beneficiary: {
|
|
139
|
+
id: identity.id,
|
|
140
|
+
publicKey: operatorKeyPair.publicKeyHex,
|
|
141
|
+
role: "beneficiary"
|
|
142
|
+
},
|
|
143
|
+
constraints: constraintSource,
|
|
144
|
+
privateKey: operatorKeyPair.privateKey,
|
|
145
|
+
enforcement: {
|
|
146
|
+
type: "monitor",
|
|
147
|
+
config: { mode: options.mode ?? "enforce" }
|
|
148
|
+
},
|
|
149
|
+
proof: {
|
|
150
|
+
type: options.proofType ?? "audit_log",
|
|
151
|
+
config: {}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
const monitor = new import_enforcement.Monitor(covenant.id, constraintSource, {
|
|
155
|
+
mode: options.mode ?? "enforce"
|
|
156
|
+
});
|
|
157
|
+
return _SteleGuard.buildWrappedServer(
|
|
158
|
+
server,
|
|
159
|
+
monitor,
|
|
160
|
+
identity,
|
|
161
|
+
covenant,
|
|
162
|
+
operatorKeyPair,
|
|
163
|
+
options
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Wrap an MCP server using a pre-built covenant document.
|
|
168
|
+
*
|
|
169
|
+
* This is useful when the covenant has been created externally
|
|
170
|
+
* (e.g. by an orchestrator or governance system) and should be
|
|
171
|
+
* used as-is without re-building.
|
|
172
|
+
*/
|
|
173
|
+
static async fromCovenant(server, covenant, operatorKeyPair) {
|
|
174
|
+
const identity = await (0, import_identity.createIdentity)({
|
|
175
|
+
operatorKeyPair,
|
|
176
|
+
model: {
|
|
177
|
+
provider: "unknown",
|
|
178
|
+
modelId: "unknown",
|
|
179
|
+
attestationType: "self_reported"
|
|
180
|
+
},
|
|
181
|
+
capabilities: (server.tools ?? []).map((t) => toolAction(t.name)),
|
|
182
|
+
deployment: {
|
|
183
|
+
runtime: "process"
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
const mode = covenant.enforcement?.config?.mode === "log_only" ? "log_only" : "enforce";
|
|
187
|
+
const monitor = new import_enforcement.Monitor(covenant.id, covenant.constraints, {
|
|
188
|
+
mode
|
|
189
|
+
});
|
|
190
|
+
return _SteleGuard.buildWrappedServer(
|
|
191
|
+
server,
|
|
192
|
+
monitor,
|
|
193
|
+
identity,
|
|
194
|
+
covenant,
|
|
195
|
+
operatorKeyPair,
|
|
196
|
+
{}
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Internal method to build the wrapped server proxy from all
|
|
201
|
+
* the initialized components.
|
|
202
|
+
*/
|
|
203
|
+
static buildWrappedServer(server, monitor, identity, covenant, operatorKeyPair, options) {
|
|
204
|
+
let totalToolCalls = 0;
|
|
205
|
+
let hasViolations = false;
|
|
206
|
+
let violationSeverity;
|
|
207
|
+
let firstCallTime;
|
|
208
|
+
let lastReceipt = null;
|
|
209
|
+
const interceptedHandleToolCall = async (name, args) => {
|
|
210
|
+
const callStart = Date.now();
|
|
211
|
+
if (firstCallTime === void 0) {
|
|
212
|
+
firstCallTime = callStart;
|
|
213
|
+
}
|
|
214
|
+
totalToolCalls++;
|
|
215
|
+
const action = toolAction(name);
|
|
216
|
+
const resource = toolResource(name, args);
|
|
217
|
+
const now = (0, import_crypto.timestamp)();
|
|
218
|
+
let permitted = true;
|
|
219
|
+
let result;
|
|
220
|
+
try {
|
|
221
|
+
await monitor.evaluate(action, resource, args);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
permitted = false;
|
|
224
|
+
const severity = extractSeverity(
|
|
225
|
+
err && typeof err === "object" && "matchedRule" in err ? err.matchedRule : void 0
|
|
226
|
+
);
|
|
227
|
+
const constraint = extractConstraint(
|
|
228
|
+
err && typeof err === "object" && "matchedRule" in err ? err.matchedRule : void 0
|
|
229
|
+
);
|
|
230
|
+
hasViolations = true;
|
|
231
|
+
violationSeverity = severity;
|
|
232
|
+
if (options.onViolation) {
|
|
233
|
+
const details = {
|
|
234
|
+
toolName: name,
|
|
235
|
+
action,
|
|
236
|
+
resource,
|
|
237
|
+
constraint,
|
|
238
|
+
severity,
|
|
239
|
+
timestamp: now
|
|
240
|
+
};
|
|
241
|
+
options.onViolation(details);
|
|
242
|
+
}
|
|
243
|
+
if (options.onToolCall) {
|
|
244
|
+
const callEnd2 = Date.now();
|
|
245
|
+
const details = {
|
|
246
|
+
toolName: name,
|
|
247
|
+
action,
|
|
248
|
+
resource,
|
|
249
|
+
permitted: false,
|
|
250
|
+
timestamp: now,
|
|
251
|
+
durationMs: callEnd2 - callStart
|
|
252
|
+
};
|
|
253
|
+
options.onToolCall(details);
|
|
254
|
+
}
|
|
255
|
+
await updateReceipt(
|
|
256
|
+
covenant,
|
|
257
|
+
identity,
|
|
258
|
+
operatorKeyPair,
|
|
259
|
+
callStart,
|
|
260
|
+
hasViolations,
|
|
261
|
+
violationSeverity,
|
|
262
|
+
lastReceipt
|
|
263
|
+
).then((r) => {
|
|
264
|
+
lastReceipt = r;
|
|
265
|
+
});
|
|
266
|
+
throw err;
|
|
267
|
+
}
|
|
268
|
+
if (server.handleToolCall) {
|
|
269
|
+
result = await server.handleToolCall(name, args);
|
|
270
|
+
} else {
|
|
271
|
+
result = void 0;
|
|
272
|
+
}
|
|
273
|
+
const callEnd = Date.now();
|
|
274
|
+
if (options.onToolCall) {
|
|
275
|
+
const details = {
|
|
276
|
+
toolName: name,
|
|
277
|
+
action,
|
|
278
|
+
resource,
|
|
279
|
+
permitted: true,
|
|
280
|
+
timestamp: now,
|
|
281
|
+
durationMs: callEnd - callStart
|
|
282
|
+
};
|
|
283
|
+
options.onToolCall(details);
|
|
284
|
+
}
|
|
285
|
+
lastReceipt = await updateReceipt(
|
|
286
|
+
covenant,
|
|
287
|
+
identity,
|
|
288
|
+
operatorKeyPair,
|
|
289
|
+
callStart,
|
|
290
|
+
hasViolations,
|
|
291
|
+
violationSeverity,
|
|
292
|
+
lastReceipt
|
|
293
|
+
);
|
|
294
|
+
return result;
|
|
295
|
+
};
|
|
296
|
+
const wrapped = /* @__PURE__ */ Object.create(null);
|
|
297
|
+
for (const key of Object.keys(server)) {
|
|
298
|
+
if (key === "handleToolCall") {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
wrapped[key] = server[key];
|
|
302
|
+
}
|
|
303
|
+
if (server.tools) {
|
|
304
|
+
wrapped.tools = server.tools;
|
|
305
|
+
}
|
|
306
|
+
wrapped.handleToolCall = interceptedHandleToolCall;
|
|
307
|
+
wrapped.getMonitor = () => monitor;
|
|
308
|
+
wrapped.getIdentity = () => identity;
|
|
309
|
+
wrapped.getAuditLog = () => monitor.getAuditLog();
|
|
310
|
+
wrapped.generateProof = async () => {
|
|
311
|
+
const auditLog = monitor.getAuditLog();
|
|
312
|
+
const auditEntries = auditLog.entries.map(
|
|
313
|
+
(entry) => ({
|
|
314
|
+
action: entry.action,
|
|
315
|
+
resource: entry.resource,
|
|
316
|
+
outcome: entry.outcome,
|
|
317
|
+
timestamp: entry.timestamp,
|
|
318
|
+
hash: entry.hash
|
|
319
|
+
})
|
|
320
|
+
);
|
|
321
|
+
return (0, import_proof.generateComplianceProof)({
|
|
322
|
+
covenantId: covenant.id,
|
|
323
|
+
constraints: covenant.constraints,
|
|
324
|
+
auditEntries
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
wrapped.getReceipt = () => lastReceipt;
|
|
328
|
+
wrapped.getCovenant = () => covenant;
|
|
329
|
+
return wrapped;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
async function updateReceipt(covenant, identity, operatorKeyPair, callStartTime, hasViolations, violationSeverity, previousReceipt) {
|
|
333
|
+
const durationMs = Date.now() - callStartTime;
|
|
334
|
+
let outcome;
|
|
335
|
+
let breachSeverity;
|
|
336
|
+
if (hasViolations) {
|
|
337
|
+
outcome = "breached";
|
|
338
|
+
breachSeverity = violationSeverity ?? "medium";
|
|
339
|
+
} else {
|
|
340
|
+
outcome = "fulfilled";
|
|
341
|
+
}
|
|
342
|
+
const proofHash = (0, import_crypto.sha256Object)({
|
|
343
|
+
covenantId: covenant.id,
|
|
344
|
+
identityId: identity.id,
|
|
345
|
+
timestamp: (0, import_crypto.timestamp)()
|
|
346
|
+
});
|
|
347
|
+
const receipt = await (0, import_reputation.createReceipt)(
|
|
348
|
+
covenant.id,
|
|
349
|
+
identity.id,
|
|
350
|
+
operatorKeyPair.publicKeyHex,
|
|
351
|
+
outcome,
|
|
352
|
+
proofHash,
|
|
353
|
+
durationMs,
|
|
354
|
+
operatorKeyPair,
|
|
355
|
+
previousReceipt?.receiptHash ?? null,
|
|
356
|
+
breachSeverity
|
|
357
|
+
);
|
|
358
|
+
return receipt;
|
|
359
|
+
}
|
|
360
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
361
|
+
0 && (module.exports = {
|
|
362
|
+
PRESETS,
|
|
363
|
+
SteleGuard
|
|
364
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { generateKeyPair, timestamp, sha256Object } from "@nobulex/crypto";
|
|
3
|
+
import { parse } from "@nobulex/ccl";
|
|
4
|
+
import { buildCovenant } from "@nobulex/core";
|
|
5
|
+
import { Monitor } from "@nobulex/enforcement";
|
|
6
|
+
import { createIdentity } from "@nobulex/identity";
|
|
7
|
+
import { createReceipt } from "@nobulex/reputation";
|
|
8
|
+
import { generateComplianceProof } from "@nobulex/proof";
|
|
9
|
+
|
|
10
|
+
// src/presets.ts
|
|
11
|
+
var PRESETS = {
|
|
12
|
+
"standard:data-isolation": `permit file.read on '/data/**'
|
|
13
|
+
permit tool.read_file on '/data/**'
|
|
14
|
+
permit tool.* on '/data/**'
|
|
15
|
+
deny file.write on '**' severity high
|
|
16
|
+
deny network.send on '**' severity critical
|
|
17
|
+
deny network.send on '**' when payload.contains_pii = true severity critical
|
|
18
|
+
require audit.log on '**' severity critical
|
|
19
|
+
limit api.call 100 per 60 seconds`,
|
|
20
|
+
"standard:read-write": `permit file.read on '/data/**'
|
|
21
|
+
permit file.write on '/output/**'
|
|
22
|
+
deny file.write on '/system/**' severity critical
|
|
23
|
+
deny network.send on '**' severity high
|
|
24
|
+
deny network.send on '**' when payload.contains_pii = true severity critical
|
|
25
|
+
require audit.log on '**' severity critical`,
|
|
26
|
+
"standard:network": `permit file.read on '/data/**'
|
|
27
|
+
permit file.write on '/output/**'
|
|
28
|
+
permit network.send on '**'
|
|
29
|
+
deny network.send on '**' when payload.contains_pii = true severity critical
|
|
30
|
+
deny file.write on '/system/**' severity critical
|
|
31
|
+
require audit.log on '**' severity critical
|
|
32
|
+
require encrypt.output on '**' when output.classification = 'sensitive' severity high
|
|
33
|
+
limit api.call 500 per 3600 seconds
|
|
34
|
+
limit network.send 100 per 60 seconds`,
|
|
35
|
+
"standard:minimal": `deny file.read on '**' severity high
|
|
36
|
+
deny file.write on '**' severity critical
|
|
37
|
+
deny network.send on '**' severity critical
|
|
38
|
+
require audit.log on '**' severity critical`
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/index.ts
|
|
42
|
+
function resolveConstraints(constraints) {
|
|
43
|
+
const trimmed = constraints.trim();
|
|
44
|
+
if (trimmed in PRESETS) {
|
|
45
|
+
return PRESETS[trimmed];
|
|
46
|
+
}
|
|
47
|
+
return trimmed;
|
|
48
|
+
}
|
|
49
|
+
function toolAction(toolName) {
|
|
50
|
+
return `tool.${toolName}`;
|
|
51
|
+
}
|
|
52
|
+
function toolResource(toolName, args) {
|
|
53
|
+
for (const key of ["path", "file", "url", "uri", "resource", "target", "name"]) {
|
|
54
|
+
const val = args[key];
|
|
55
|
+
if (typeof val === "string" && val.length > 0) {
|
|
56
|
+
return val;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return `/tool/${toolName}`;
|
|
60
|
+
}
|
|
61
|
+
function extractSeverity(matchedRule) {
|
|
62
|
+
if (matchedRule && typeof matchedRule === "object" && "severity" in matchedRule && typeof matchedRule.severity === "string") {
|
|
63
|
+
return matchedRule.severity;
|
|
64
|
+
}
|
|
65
|
+
return "medium";
|
|
66
|
+
}
|
|
67
|
+
function extractConstraint(matchedRule) {
|
|
68
|
+
if (matchedRule && typeof matchedRule === "object" && "type" in matchedRule) {
|
|
69
|
+
const rule = matchedRule;
|
|
70
|
+
const type = rule.type;
|
|
71
|
+
const action = rule.action ?? "*";
|
|
72
|
+
const resource = rule.resource ?? "*";
|
|
73
|
+
return `${type} ${action} on '${resource}'`;
|
|
74
|
+
}
|
|
75
|
+
return "default deny (no matching permit rule)";
|
|
76
|
+
}
|
|
77
|
+
var SteleGuard = class _SteleGuard {
|
|
78
|
+
// Private constructor - use static factory methods
|
|
79
|
+
constructor() {
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Wrap an MCP server with Stele accountability using constraint text
|
|
83
|
+
* (either a preset name or raw CCL).
|
|
84
|
+
*
|
|
85
|
+
* Generates a keypair if one is not provided, creates an agent identity,
|
|
86
|
+
* builds a covenant document, and returns a wrapped server that intercepts
|
|
87
|
+
* every tool call through the constraint monitor.
|
|
88
|
+
*/
|
|
89
|
+
static async wrap(server, options) {
|
|
90
|
+
const constraintSource = resolveConstraints(options.constraints);
|
|
91
|
+
parse(constraintSource);
|
|
92
|
+
const operatorKeyPair = options.operatorKeyPair ?? await generateKeyPair();
|
|
93
|
+
const model = options.model ?? {
|
|
94
|
+
provider: "unknown",
|
|
95
|
+
modelId: "unknown",
|
|
96
|
+
attestationType: "self_reported"
|
|
97
|
+
};
|
|
98
|
+
const identity = await createIdentity({
|
|
99
|
+
operatorKeyPair,
|
|
100
|
+
operatorIdentifier: options.agentIdentifier,
|
|
101
|
+
model,
|
|
102
|
+
capabilities: (server.tools ?? []).map((t) => toolAction(t.name)),
|
|
103
|
+
deployment: {
|
|
104
|
+
runtime: "process"
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
const covenant = await buildCovenant({
|
|
108
|
+
issuer: {
|
|
109
|
+
id: options.agentIdentifier ?? operatorKeyPair.publicKeyHex,
|
|
110
|
+
publicKey: operatorKeyPair.publicKeyHex,
|
|
111
|
+
role: "issuer"
|
|
112
|
+
},
|
|
113
|
+
beneficiary: {
|
|
114
|
+
id: identity.id,
|
|
115
|
+
publicKey: operatorKeyPair.publicKeyHex,
|
|
116
|
+
role: "beneficiary"
|
|
117
|
+
},
|
|
118
|
+
constraints: constraintSource,
|
|
119
|
+
privateKey: operatorKeyPair.privateKey,
|
|
120
|
+
enforcement: {
|
|
121
|
+
type: "monitor",
|
|
122
|
+
config: { mode: options.mode ?? "enforce" }
|
|
123
|
+
},
|
|
124
|
+
proof: {
|
|
125
|
+
type: options.proofType ?? "audit_log",
|
|
126
|
+
config: {}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
const monitor = new Monitor(covenant.id, constraintSource, {
|
|
130
|
+
mode: options.mode ?? "enforce"
|
|
131
|
+
});
|
|
132
|
+
return _SteleGuard.buildWrappedServer(
|
|
133
|
+
server,
|
|
134
|
+
monitor,
|
|
135
|
+
identity,
|
|
136
|
+
covenant,
|
|
137
|
+
operatorKeyPair,
|
|
138
|
+
options
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Wrap an MCP server using a pre-built covenant document.
|
|
143
|
+
*
|
|
144
|
+
* This is useful when the covenant has been created externally
|
|
145
|
+
* (e.g. by an orchestrator or governance system) and should be
|
|
146
|
+
* used as-is without re-building.
|
|
147
|
+
*/
|
|
148
|
+
static async fromCovenant(server, covenant, operatorKeyPair) {
|
|
149
|
+
const identity = await createIdentity({
|
|
150
|
+
operatorKeyPair,
|
|
151
|
+
model: {
|
|
152
|
+
provider: "unknown",
|
|
153
|
+
modelId: "unknown",
|
|
154
|
+
attestationType: "self_reported"
|
|
155
|
+
},
|
|
156
|
+
capabilities: (server.tools ?? []).map((t) => toolAction(t.name)),
|
|
157
|
+
deployment: {
|
|
158
|
+
runtime: "process"
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
const mode = covenant.enforcement?.config?.mode === "log_only" ? "log_only" : "enforce";
|
|
162
|
+
const monitor = new Monitor(covenant.id, covenant.constraints, {
|
|
163
|
+
mode
|
|
164
|
+
});
|
|
165
|
+
return _SteleGuard.buildWrappedServer(
|
|
166
|
+
server,
|
|
167
|
+
monitor,
|
|
168
|
+
identity,
|
|
169
|
+
covenant,
|
|
170
|
+
operatorKeyPair,
|
|
171
|
+
{}
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Internal method to build the wrapped server proxy from all
|
|
176
|
+
* the initialized components.
|
|
177
|
+
*/
|
|
178
|
+
static buildWrappedServer(server, monitor, identity, covenant, operatorKeyPair, options) {
|
|
179
|
+
let totalToolCalls = 0;
|
|
180
|
+
let hasViolations = false;
|
|
181
|
+
let violationSeverity;
|
|
182
|
+
let firstCallTime;
|
|
183
|
+
let lastReceipt = null;
|
|
184
|
+
const interceptedHandleToolCall = async (name, args) => {
|
|
185
|
+
const callStart = Date.now();
|
|
186
|
+
if (firstCallTime === void 0) {
|
|
187
|
+
firstCallTime = callStart;
|
|
188
|
+
}
|
|
189
|
+
totalToolCalls++;
|
|
190
|
+
const action = toolAction(name);
|
|
191
|
+
const resource = toolResource(name, args);
|
|
192
|
+
const now = timestamp();
|
|
193
|
+
let permitted = true;
|
|
194
|
+
let result;
|
|
195
|
+
try {
|
|
196
|
+
await monitor.evaluate(action, resource, args);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
permitted = false;
|
|
199
|
+
const severity = extractSeverity(
|
|
200
|
+
err && typeof err === "object" && "matchedRule" in err ? err.matchedRule : void 0
|
|
201
|
+
);
|
|
202
|
+
const constraint = extractConstraint(
|
|
203
|
+
err && typeof err === "object" && "matchedRule" in err ? err.matchedRule : void 0
|
|
204
|
+
);
|
|
205
|
+
hasViolations = true;
|
|
206
|
+
violationSeverity = severity;
|
|
207
|
+
if (options.onViolation) {
|
|
208
|
+
const details = {
|
|
209
|
+
toolName: name,
|
|
210
|
+
action,
|
|
211
|
+
resource,
|
|
212
|
+
constraint,
|
|
213
|
+
severity,
|
|
214
|
+
timestamp: now
|
|
215
|
+
};
|
|
216
|
+
options.onViolation(details);
|
|
217
|
+
}
|
|
218
|
+
if (options.onToolCall) {
|
|
219
|
+
const callEnd2 = Date.now();
|
|
220
|
+
const details = {
|
|
221
|
+
toolName: name,
|
|
222
|
+
action,
|
|
223
|
+
resource,
|
|
224
|
+
permitted: false,
|
|
225
|
+
timestamp: now,
|
|
226
|
+
durationMs: callEnd2 - callStart
|
|
227
|
+
};
|
|
228
|
+
options.onToolCall(details);
|
|
229
|
+
}
|
|
230
|
+
await updateReceipt(
|
|
231
|
+
covenant,
|
|
232
|
+
identity,
|
|
233
|
+
operatorKeyPair,
|
|
234
|
+
callStart,
|
|
235
|
+
hasViolations,
|
|
236
|
+
violationSeverity,
|
|
237
|
+
lastReceipt
|
|
238
|
+
).then((r) => {
|
|
239
|
+
lastReceipt = r;
|
|
240
|
+
});
|
|
241
|
+
throw err;
|
|
242
|
+
}
|
|
243
|
+
if (server.handleToolCall) {
|
|
244
|
+
result = await server.handleToolCall(name, args);
|
|
245
|
+
} else {
|
|
246
|
+
result = void 0;
|
|
247
|
+
}
|
|
248
|
+
const callEnd = Date.now();
|
|
249
|
+
if (options.onToolCall) {
|
|
250
|
+
const details = {
|
|
251
|
+
toolName: name,
|
|
252
|
+
action,
|
|
253
|
+
resource,
|
|
254
|
+
permitted: true,
|
|
255
|
+
timestamp: now,
|
|
256
|
+
durationMs: callEnd - callStart
|
|
257
|
+
};
|
|
258
|
+
options.onToolCall(details);
|
|
259
|
+
}
|
|
260
|
+
lastReceipt = await updateReceipt(
|
|
261
|
+
covenant,
|
|
262
|
+
identity,
|
|
263
|
+
operatorKeyPair,
|
|
264
|
+
callStart,
|
|
265
|
+
hasViolations,
|
|
266
|
+
violationSeverity,
|
|
267
|
+
lastReceipt
|
|
268
|
+
);
|
|
269
|
+
return result;
|
|
270
|
+
};
|
|
271
|
+
const wrapped = /* @__PURE__ */ Object.create(null);
|
|
272
|
+
for (const key of Object.keys(server)) {
|
|
273
|
+
if (key === "handleToolCall") {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
wrapped[key] = server[key];
|
|
277
|
+
}
|
|
278
|
+
if (server.tools) {
|
|
279
|
+
wrapped.tools = server.tools;
|
|
280
|
+
}
|
|
281
|
+
wrapped.handleToolCall = interceptedHandleToolCall;
|
|
282
|
+
wrapped.getMonitor = () => monitor;
|
|
283
|
+
wrapped.getIdentity = () => identity;
|
|
284
|
+
wrapped.getAuditLog = () => monitor.getAuditLog();
|
|
285
|
+
wrapped.generateProof = async () => {
|
|
286
|
+
const auditLog = monitor.getAuditLog();
|
|
287
|
+
const auditEntries = auditLog.entries.map(
|
|
288
|
+
(entry) => ({
|
|
289
|
+
action: entry.action,
|
|
290
|
+
resource: entry.resource,
|
|
291
|
+
outcome: entry.outcome,
|
|
292
|
+
timestamp: entry.timestamp,
|
|
293
|
+
hash: entry.hash
|
|
294
|
+
})
|
|
295
|
+
);
|
|
296
|
+
return generateComplianceProof({
|
|
297
|
+
covenantId: covenant.id,
|
|
298
|
+
constraints: covenant.constraints,
|
|
299
|
+
auditEntries
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
wrapped.getReceipt = () => lastReceipt;
|
|
303
|
+
wrapped.getCovenant = () => covenant;
|
|
304
|
+
return wrapped;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
async function updateReceipt(covenant, identity, operatorKeyPair, callStartTime, hasViolations, violationSeverity, previousReceipt) {
|
|
308
|
+
const durationMs = Date.now() - callStartTime;
|
|
309
|
+
let outcome;
|
|
310
|
+
let breachSeverity;
|
|
311
|
+
if (hasViolations) {
|
|
312
|
+
outcome = "breached";
|
|
313
|
+
breachSeverity = violationSeverity ?? "medium";
|
|
314
|
+
} else {
|
|
315
|
+
outcome = "fulfilled";
|
|
316
|
+
}
|
|
317
|
+
const proofHash = sha256Object({
|
|
318
|
+
covenantId: covenant.id,
|
|
319
|
+
identityId: identity.id,
|
|
320
|
+
timestamp: timestamp()
|
|
321
|
+
});
|
|
322
|
+
const receipt = await createReceipt(
|
|
323
|
+
covenant.id,
|
|
324
|
+
identity.id,
|
|
325
|
+
operatorKeyPair.publicKeyHex,
|
|
326
|
+
outcome,
|
|
327
|
+
proofHash,
|
|
328
|
+
durationMs,
|
|
329
|
+
operatorKeyPair,
|
|
330
|
+
previousReceipt?.receiptHash ?? null,
|
|
331
|
+
breachSeverity
|
|
332
|
+
);
|
|
333
|
+
return receipt;
|
|
334
|
+
}
|
|
335
|
+
export {
|
|
336
|
+
PRESETS,
|
|
337
|
+
SteleGuard
|
|
338
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nobulex/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"sideEffects": false,
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup src/index.ts --format cjs,esm",
|
|
17
|
+
"bundle": "tsup src/index.ts --format cjs,esm",
|
|
18
|
+
"dev": "tsup src/index.ts --format cjs,esm --watch",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@nobulex/crypto": "0.1.0",
|
|
28
|
+
"@nobulex/ccl": "0.1.0",
|
|
29
|
+
"@nobulex/core": "0.1.0",
|
|
30
|
+
"@nobulex/enforcement": "0.1.0",
|
|
31
|
+
"@nobulex/identity": "0.1.0",
|
|
32
|
+
"@nobulex/reputation": "0.1.0",
|
|
33
|
+
"@nobulex/proof": "0.1.0"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/agbusiness195/Stele.git",
|
|
45
|
+
"directory": "packages/mcp"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"stele",
|
|
49
|
+
"covenant",
|
|
50
|
+
"ai-accountability"
|
|
51
|
+
]
|
|
52
|
+
}
|