pluribus-context 0.3.37 → 0.3.38
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/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
All notable changes to Pluribus are documented here.
|
|
6
6
|
|
|
7
|
+
## 0.3.38 - 2026-06-06
|
|
8
|
+
|
|
9
|
+
- Added `pluribus demo mcp-audit-receipt`, a tiny npm-runnable demo that validates privacy-safe MCP tool-call audit events and low-cardinality usage metrics without logging raw prompts, args, results, tokens, or row data.
|
|
10
|
+
|
|
7
11
|
## 0.3.37 - 2026-06-06
|
|
8
12
|
|
|
9
13
|
- Published the canonical top-level Agent Skills layout (`skills/*/SKILL.md`) and backwards-compatible legacy mirrors so external skill registries can keep source links verifiable while package users get the current recipes from npm.
|
package/bin/pluribus.js
CHANGED
|
@@ -67,7 +67,7 @@ OPTIONS (watch)
|
|
|
67
67
|
--debounce Debounce delay in ms (minimum 300, default 400)
|
|
68
68
|
|
|
69
69
|
OPTIONS (demo)
|
|
70
|
-
--receipt Validate a custom
|
|
70
|
+
--receipt Validate a custom demo receipt JSON file
|
|
71
71
|
--json Print machine-readable demo results
|
|
72
72
|
|
|
73
73
|
EXAMPLES
|
|
@@ -89,6 +89,8 @@ EXAMPLES
|
|
|
89
89
|
pluribus watch --tools claude,cursor
|
|
90
90
|
pluribus demo skill-use-rate
|
|
91
91
|
pluribus demo skill-use-rate --json
|
|
92
|
+
pluribus demo mcp-audit-receipt
|
|
93
|
+
pluribus demo mcp-audit-receipt --json
|
|
92
94
|
|
|
93
95
|
DOCS
|
|
94
96
|
https://github.com/caioribeiroclw-pixel/pluribus
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# MCP audit receipt demo
|
|
2
|
+
|
|
3
|
+
This example validates a privacy-safe audit receipt for MCP `tools/call` activity.
|
|
4
|
+
|
|
5
|
+
Run from any directory after `pluribus-context@latest` is published:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx --yes pluribus-context@latest demo mcp-audit-receipt
|
|
9
|
+
npx --yes pluribus-context@latest demo mcp-audit-receipt --json
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Or validate your own receipt shape:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx --yes pluribus-context@latest demo mcp-audit-receipt --receipt ./mcp-audit-receipt.json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The point is to split:
|
|
19
|
+
|
|
20
|
+
- **audit events**: correlation IDs, hashed user/token subject, token scopes, tool name, redacted argument shape, status, duration, result shape, and error class;
|
|
21
|
+
- **usage metrics**: low-cardinality counters/histograms such as tool name, status, and token scope;
|
|
22
|
+
- **privacy boundary**: no raw prompts, raw SQL, row data, tokens, tool outputs, or private connection strings in the receipt.
|
|
23
|
+
|
|
24
|
+
This is for MCP server/gateway operators who need to answer: “who invoked which tool, under what scope, and did it succeed?” without dumping sensitive content into logs.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "pluribus.mcp_tool_call_audit_receipt.v1",
|
|
3
|
+
"run_id": "mcp-audit-2026-06-06T22:00Z",
|
|
4
|
+
"generated_at": "2026-06-06T22:00:00Z",
|
|
5
|
+
"server": {
|
|
6
|
+
"name": "analytics-mcp",
|
|
7
|
+
"transport": "http",
|
|
8
|
+
"version": "1.4.0"
|
|
9
|
+
},
|
|
10
|
+
"client": {
|
|
11
|
+
"name": "claude-desktop",
|
|
12
|
+
"workspace": "hashed:7f3f0f5f"
|
|
13
|
+
},
|
|
14
|
+
"audit_policy": {
|
|
15
|
+
"raw_arguments": "redacted_shape_only",
|
|
16
|
+
"raw_results": "redacted_shape_only",
|
|
17
|
+
"privacy_boundary": "no prompts, raw SQL, row data, tokens, or private connection strings in receipt"
|
|
18
|
+
},
|
|
19
|
+
"tool_calls": [
|
|
20
|
+
{
|
|
21
|
+
"event": "mcp.tool_call",
|
|
22
|
+
"request_id": "req_01JY6GATEWAY",
|
|
23
|
+
"session_id": "sess_01JY6RUN",
|
|
24
|
+
"user_id_hash": "sha256:0d9b4a1a3f0a6f2fd0dc8aa9d0c6f2b7a35f4f5d0b2a4d3e1e04a8b4b6b2e5d8",
|
|
25
|
+
"token_subject_hash": "sha256:6aaf4a3b4d10c45c8fd2cb4f3c73b8cde42bb62779b7e1c6a2c5e0dd8d78f4a1",
|
|
26
|
+
"token_scopes": ["database:read", "query:run"],
|
|
27
|
+
"tool_name": "query_database",
|
|
28
|
+
"args_shape": {
|
|
29
|
+
"database_id": "number",
|
|
30
|
+
"query": "redacted_sql",
|
|
31
|
+
"limit": "number"
|
|
32
|
+
},
|
|
33
|
+
"status": "ok",
|
|
34
|
+
"duration_ms": 184,
|
|
35
|
+
"result_shape": "rows:12 columns:4",
|
|
36
|
+
"error_class": null
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"event": "mcp.tool_call",
|
|
40
|
+
"request_id": "req_01JY6DENIED",
|
|
41
|
+
"session_id": "sess_01JY6RUN",
|
|
42
|
+
"user_id_hash": "sha256:0d9b4a1a3f0a6f2fd0dc8aa9d0c6f2b7a35f4f5d0b2a4d3e1e04a8b4b6b2e5d8",
|
|
43
|
+
"token_subject_hash": "sha256:6aaf4a3b4d10c45c8fd2cb4f3c73b8cde42bb62779b7e1c6a2c5e0dd8d78f4a1",
|
|
44
|
+
"token_scopes": ["database:read"],
|
|
45
|
+
"tool_name": "update_dashboard",
|
|
46
|
+
"args_shape": {
|
|
47
|
+
"dashboard_id": "number",
|
|
48
|
+
"mutation": "redacted_object"
|
|
49
|
+
},
|
|
50
|
+
"status": "denied",
|
|
51
|
+
"duration_ms": 12,
|
|
52
|
+
"result_shape": "policy_denial",
|
|
53
|
+
"error_class": "insufficient_scope"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"usage_metrics": [
|
|
57
|
+
{
|
|
58
|
+
"name": "mcp_tool_calls_total",
|
|
59
|
+
"type": "counter",
|
|
60
|
+
"value": "1",
|
|
61
|
+
"labels": ["tool_name", "status", "token_scope"]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "mcp_tool_call_duration_ms",
|
|
65
|
+
"type": "histogram",
|
|
66
|
+
"value": "184",
|
|
67
|
+
"labels": ["tool_name", "status"]
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pluribus-context",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.38",
|
|
4
4
|
"description": "AI context and rules sync CLI for Claude.md, Claude Code, Cursor, and Copilot instructions, with privacy-safe context receipts that prove what memory, tools, skills, compactions, and security findings crossed agent boundaries without logging raw content.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/caioribeiroclw-pixel/pluribus#readme",
|
package/src/commands/demo.js
CHANGED
|
@@ -7,7 +7,11 @@ import * as path from 'path'
|
|
|
7
7
|
import { fileURLToPath } from 'url'
|
|
8
8
|
|
|
9
9
|
const DEFAULT_DEMO = 'skill-use-rate'
|
|
10
|
+
const SKILL_USE_RATE_DEMO = 'skill-use-rate'
|
|
11
|
+
const MCP_AUDIT_RECEIPT_DEMO = 'mcp-audit-receipt'
|
|
12
|
+
const AVAILABLE_DEMOS = [SKILL_USE_RATE_DEMO, MCP_AUDIT_RECEIPT_DEMO]
|
|
10
13
|
const SKILL_USE_RATE_SCHEMA = 'pluribus.skill_use_rate_receipt.v1'
|
|
14
|
+
const MCP_AUDIT_RECEIPT_SCHEMA = 'pluribus.mcp_tool_call_audit_receipt.v1'
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* @param {Record<string, string | boolean>} args
|
|
@@ -16,29 +20,42 @@ const SKILL_USE_RATE_SCHEMA = 'pluribus.skill_use_rate_receipt.v1'
|
|
|
16
20
|
export async function runDemo(args, positional = []) {
|
|
17
21
|
const demoName = positional[0] || DEFAULT_DEMO
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
switch (demoName) {
|
|
24
|
+
case SKILL_USE_RATE_DEMO:
|
|
25
|
+
return runSkillUseRateDemo(args)
|
|
26
|
+
case MCP_AUDIT_RECEIPT_DEMO:
|
|
27
|
+
return runMcpAuditReceiptDemo(args)
|
|
28
|
+
default:
|
|
29
|
+
console.error(`❌ Unknown demo: ${demoName}`)
|
|
30
|
+
console.error(` Available demos: ${AVAILABLE_DEMOS.join(', ')}`)
|
|
31
|
+
process.exit(1)
|
|
23
32
|
}
|
|
33
|
+
}
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
? path.resolve(process.cwd(), args.receipt)
|
|
27
|
-
: bundledSkillUseRateReceiptPath()
|
|
28
|
-
|
|
29
|
-
let receipt
|
|
35
|
+
function readReceipt(receiptPath, label) {
|
|
30
36
|
try {
|
|
31
|
-
|
|
37
|
+
return JSON.parse(fs.readFileSync(receiptPath, 'utf8'))
|
|
32
38
|
} catch (err) {
|
|
33
|
-
console.error(`❌ Could not read
|
|
39
|
+
console.error(`❌ Could not read ${label} receipt at ${receiptPath}: ${err.message}`)
|
|
34
40
|
process.exit(1)
|
|
35
41
|
}
|
|
42
|
+
}
|
|
36
43
|
|
|
44
|
+
function selectedReceiptPath(args, defaultPath) {
|
|
45
|
+
return typeof args.receipt === 'string' && args.receipt.trim()
|
|
46
|
+
? path.resolve(process.cwd(), args.receipt)
|
|
47
|
+
: defaultPath
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function runSkillUseRateDemo(args) {
|
|
51
|
+
const receiptPath = selectedReceiptPath(args, bundledSkillUseRateReceiptPath())
|
|
52
|
+
const receipt = readReceipt(receiptPath, 'skill use-rate')
|
|
37
53
|
const result = validateSkillUseRateReceipt(receipt)
|
|
54
|
+
|
|
38
55
|
if (Boolean(args.json)) {
|
|
39
56
|
console.log(JSON.stringify({
|
|
40
57
|
ok: result.errors.length === 0,
|
|
41
|
-
demo:
|
|
58
|
+
demo: SKILL_USE_RATE_DEMO,
|
|
42
59
|
receipt: path.relative(process.cwd(), receiptPath) || receiptPath,
|
|
43
60
|
summary: result.summary,
|
|
44
61
|
warnings: result.warnings,
|
|
@@ -65,10 +82,48 @@ export async function runDemo(args, positional = []) {
|
|
|
65
82
|
if (result.errors.length > 0) process.exit(1)
|
|
66
83
|
}
|
|
67
84
|
|
|
85
|
+
function runMcpAuditReceiptDemo(args) {
|
|
86
|
+
const receiptPath = selectedReceiptPath(args, bundledMcpAuditReceiptPath())
|
|
87
|
+
const receipt = readReceipt(receiptPath, 'MCP audit')
|
|
88
|
+
const result = validateMcpAuditReceipt(receipt)
|
|
89
|
+
|
|
90
|
+
if (Boolean(args.json)) {
|
|
91
|
+
console.log(JSON.stringify({
|
|
92
|
+
ok: result.errors.length === 0,
|
|
93
|
+
demo: MCP_AUDIT_RECEIPT_DEMO,
|
|
94
|
+
receipt: path.relative(process.cwd(), receiptPath) || receiptPath,
|
|
95
|
+
summary: result.summary,
|
|
96
|
+
warnings: result.warnings,
|
|
97
|
+
errors: result.errors,
|
|
98
|
+
}, null, 2))
|
|
99
|
+
} else {
|
|
100
|
+
console.log('🧪 Pluribus demo: MCP audit receipt')
|
|
101
|
+
console.log(` Receipt: ${path.relative(process.cwd(), receiptPath) || receiptPath}`)
|
|
102
|
+
console.log('')
|
|
103
|
+
|
|
104
|
+
if (result.errors.length === 0) {
|
|
105
|
+
console.log(`✅ MCP audit receipt ok: ${result.summary.toolCallCount} tool calls, ${result.summary.auditEventCount} audit events, ${result.summary.metricCount} metrics`)
|
|
106
|
+
for (const warning of result.warnings) console.log(` • ${warning}`)
|
|
107
|
+
console.log('')
|
|
108
|
+
console.log('Why this matters: production MCP needs audit events and low-cardinality metrics, not raw prompt/tool dumps. Prove who invoked which tool, under which scope, with redacted argument/result shape.')
|
|
109
|
+
console.log('Try your own receipt: pluribus demo mcp-audit-receipt --receipt path/to/mcp-audit-receipt.json')
|
|
110
|
+
} else {
|
|
111
|
+
console.error('❌ MCP audit receipt invalid:')
|
|
112
|
+
for (const error of result.errors) console.error(` • ${error}`)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (result.errors.length > 0) process.exit(1)
|
|
117
|
+
}
|
|
118
|
+
|
|
68
119
|
function bundledSkillUseRateReceiptPath() {
|
|
69
120
|
return fileURLToPath(new URL('../../examples/skill-use-rate-receipts/skill-use-rate-receipt.json', import.meta.url))
|
|
70
121
|
}
|
|
71
122
|
|
|
123
|
+
function bundledMcpAuditReceiptPath() {
|
|
124
|
+
return fileURLToPath(new URL('../../examples/mcp-audit-receipts/mcp-audit-receipt.json', import.meta.url))
|
|
125
|
+
}
|
|
126
|
+
|
|
72
127
|
export function validateSkillUseRateReceipt(receipt) {
|
|
73
128
|
const errors = []
|
|
74
129
|
const warnings = []
|
|
@@ -153,3 +208,102 @@ export function validateSkillUseRateReceipt(receipt) {
|
|
|
153
208
|
},
|
|
154
209
|
}
|
|
155
210
|
}
|
|
211
|
+
|
|
212
|
+
export function validateMcpAuditReceipt(receipt) {
|
|
213
|
+
const errors = []
|
|
214
|
+
const warnings = []
|
|
215
|
+
|
|
216
|
+
function requireString(value, field) {
|
|
217
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
218
|
+
errors.push(`${field} must be a non-empty string`)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function requireArray(value, field) {
|
|
223
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
224
|
+
errors.push(`${field} must be a non-empty array`)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function requireNonNegativeNumber(value, field) {
|
|
229
|
+
if (typeof value !== 'number' || Number.isNaN(value) || value < 0) {
|
|
230
|
+
errors.push(`${field} must be a non-negative number`)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (receipt.schema !== MCP_AUDIT_RECEIPT_SCHEMA) {
|
|
235
|
+
errors.push(`schema must be ${MCP_AUDIT_RECEIPT_SCHEMA}`)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
requireString(receipt.run_id, 'run_id')
|
|
239
|
+
requireString(receipt.generated_at, 'generated_at')
|
|
240
|
+
requireString(receipt.server?.name, 'server.name')
|
|
241
|
+
requireString(receipt.server?.transport, 'server.transport')
|
|
242
|
+
requireString(receipt.client?.name, 'client.name')
|
|
243
|
+
requireString(receipt.audit_policy?.raw_arguments, 'audit_policy.raw_arguments')
|
|
244
|
+
requireString(receipt.audit_policy?.raw_results, 'audit_policy.raw_results')
|
|
245
|
+
requireString(receipt.audit_policy?.privacy_boundary, 'audit_policy.privacy_boundary')
|
|
246
|
+
requireArray(receipt.tool_calls, 'tool_calls')
|
|
247
|
+
requireArray(receipt.usage_metrics, 'usage_metrics')
|
|
248
|
+
|
|
249
|
+
if (receipt.audit_policy?.raw_arguments !== 'redacted_shape_only') {
|
|
250
|
+
errors.push('audit_policy.raw_arguments must be redacted_shape_only')
|
|
251
|
+
}
|
|
252
|
+
if (receipt.audit_policy?.raw_results !== 'redacted_shape_only') {
|
|
253
|
+
errors.push('audit_policy.raw_results must be redacted_shape_only')
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const lowCardinalityMetricLabels = new Set(['tool_name', 'status', 'token_scope', 'user_type'])
|
|
257
|
+
|
|
258
|
+
for (const [index, call] of (receipt.tool_calls || []).entries()) {
|
|
259
|
+
const prefix = `tool_calls[${index}]`
|
|
260
|
+
requireString(call.event, `${prefix}.event`)
|
|
261
|
+
requireString(call.request_id, `${prefix}.request_id`)
|
|
262
|
+
requireString(call.session_id, `${prefix}.session_id`)
|
|
263
|
+
requireString(call.user_id_hash, `${prefix}.user_id_hash`)
|
|
264
|
+
requireString(call.token_subject_hash, `${prefix}.token_subject_hash`)
|
|
265
|
+
requireArray(call.token_scopes, `${prefix}.token_scopes`)
|
|
266
|
+
requireString(call.tool_name, `${prefix}.tool_name`)
|
|
267
|
+
requireString(call.status, `${prefix}.status`)
|
|
268
|
+
requireNonNegativeNumber(call.duration_ms, `${prefix}.duration_ms`)
|
|
269
|
+
requireString(call.result_shape, `${prefix}.result_shape`)
|
|
270
|
+
|
|
271
|
+
if (call.event !== 'mcp.tool_call') errors.push(`${prefix}.event must be mcp.tool_call`)
|
|
272
|
+
if (!['ok', 'empty', 'error', 'timeout', 'denied'].includes(call.status)) {
|
|
273
|
+
errors.push(`${prefix}.status must be one of ok|empty|error|timeout|denied`)
|
|
274
|
+
}
|
|
275
|
+
if (!call.args_shape || typeof call.args_shape !== 'object' || Array.isArray(call.args_shape)) {
|
|
276
|
+
errors.push(`${prefix}.args_shape must be an object with redacted argument types/shapes`)
|
|
277
|
+
}
|
|
278
|
+
if (typeof call.args_preview === 'string' || typeof call.result_preview === 'string') {
|
|
279
|
+
errors.push(`${prefix} must not include raw args/results previews; use args_shape/result_shape instead`)
|
|
280
|
+
}
|
|
281
|
+
if (call.error_class != null && typeof call.error_class !== 'string') {
|
|
282
|
+
errors.push(`${prefix}.error_class must be string or null`)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (const [index, metric] of (receipt.usage_metrics || []).entries()) {
|
|
287
|
+
const prefix = `usage_metrics[${index}]`
|
|
288
|
+
requireString(metric.name, `${prefix}.name`)
|
|
289
|
+
requireString(metric.type, `${prefix}.type`)
|
|
290
|
+
requireString(metric.value, `${prefix}.value`)
|
|
291
|
+
requireArray(metric.labels, `${prefix}.labels`)
|
|
292
|
+
|
|
293
|
+
for (const label of metric.labels || []) {
|
|
294
|
+
if (!lowCardinalityMetricLabels.has(label)) {
|
|
295
|
+
warnings.push(`${prefix}.labels includes high-cardinality label ${label}; prefer ${[...lowCardinalityMetricLabels].join(', ')}`)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
errors,
|
|
302
|
+
warnings,
|
|
303
|
+
summary: {
|
|
304
|
+
toolCallCount: Array.isArray(receipt.tool_calls) ? receipt.tool_calls.length : 0,
|
|
305
|
+
auditEventCount: Array.isArray(receipt.tool_calls) ? receipt.tool_calls.length : 0,
|
|
306
|
+
metricCount: Array.isArray(receipt.usage_metrics) ? receipt.usage_metrics.length : 0,
|
|
307
|
+
},
|
|
308
|
+
}
|
|
309
|
+
}
|
package/src/utils/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.3.
|
|
1
|
+
export const VERSION = '0.3.38'
|