@scopeblind/verify-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/README.md +87 -0
- package/package.json +47 -0
- package/samples/sample-bundle.json +78 -0
- package/samples/sample-receipt.json +19 -0
- package/server.js +246 -0
- package/server.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @scopeblind/verify-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for offline verification of ScopeBlind and Veritas Acta artifacts.
|
|
4
|
+
|
|
5
|
+
It is deliberately narrow:
|
|
6
|
+
- verify a single signed receipt or artifact
|
|
7
|
+
- verify an audit bundle offline
|
|
8
|
+
- explain a signed artifact in normalized form
|
|
9
|
+
- run a packaged self-test so clients can prove the verifier works
|
|
10
|
+
|
|
11
|
+
This is the registry-worthy MCP surface for the verification lane. It is not a gateway, not a builder, and not a hosted verification service.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g @scopeblind/verify-mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Claude Desktop / MCP config
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"scopeblind-verify": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["-y", "@scopeblind/verify-mcp"]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Tools
|
|
33
|
+
|
|
34
|
+
### `self_test`
|
|
35
|
+
Runs packaged sample verification.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
- sample receipt valid / invalid
|
|
39
|
+
- sample bundle valid / invalid
|
|
40
|
+
- total receipts in the sample bundle
|
|
41
|
+
|
|
42
|
+
### `verify_receipt`
|
|
43
|
+
Inputs:
|
|
44
|
+
- `artifact_json` or `path`
|
|
45
|
+
- optional `public_key_hex`
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
- valid / invalid
|
|
49
|
+
- type
|
|
50
|
+
- format
|
|
51
|
+
- issuer
|
|
52
|
+
- kid
|
|
53
|
+
- canonical hash
|
|
54
|
+
|
|
55
|
+
### `verify_bundle`
|
|
56
|
+
Inputs:
|
|
57
|
+
- `bundle_json` or `path`
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
- valid / invalid
|
|
61
|
+
- total receipts
|
|
62
|
+
- passed
|
|
63
|
+
- failed
|
|
64
|
+
|
|
65
|
+
### `explain_artifact`
|
|
66
|
+
Inputs:
|
|
67
|
+
- `artifact_json` or `path`
|
|
68
|
+
|
|
69
|
+
Returns a normalized summary of:
|
|
70
|
+
- type
|
|
71
|
+
- format
|
|
72
|
+
- issuer
|
|
73
|
+
- kid
|
|
74
|
+
- issued_at / timestamp
|
|
75
|
+
- payload keys
|
|
76
|
+
|
|
77
|
+
## Notes
|
|
78
|
+
|
|
79
|
+
- No ScopeBlind servers are contacted.
|
|
80
|
+
- This server verifies local JSON artifacts only.
|
|
81
|
+
- `protect-mcp` remains the local policy gateway.
|
|
82
|
+
- `@scopeblind/passport` remains the local pack builder.
|
|
83
|
+
- `@scopeblind/red-team` remains the local benchmark runner.
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scopeblind/verify-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for offline verification of ScopeBlind and Veritas Acta artifacts.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"scopeblind-verify-mcp": "./server.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"server.js",
|
|
12
|
+
"README.md",
|
|
13
|
+
"server.json",
|
|
14
|
+
"samples/"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
18
|
+
"@veritasacta/artifacts": "^0.2.0",
|
|
19
|
+
"zod": "^3.24.0"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"model-context-protocol",
|
|
27
|
+
"scopeblind",
|
|
28
|
+
"veritasacta",
|
|
29
|
+
"artifact-verification",
|
|
30
|
+
"receipts",
|
|
31
|
+
"offline-verification"
|
|
32
|
+
],
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/tomjwxf/scopeblind-gateway.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://scopeblind.com/verify",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/tomjwxf/scopeblind-gateway/issues"
|
|
40
|
+
},
|
|
41
|
+
"mcpName": "io.github.tomjwxf/verify-mcp",
|
|
42
|
+
"scripts": {
|
|
43
|
+
"check": "node --check server.js",
|
|
44
|
+
"smoke": "node test/smoke.mjs",
|
|
45
|
+
"prepublishOnly": "npm run check && npm run smoke"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": "scopeblind:audit-bundle:v1",
|
|
3
|
+
"generated_at": "2026-03-22T00:00:00Z",
|
|
4
|
+
"issuer": "protect-mcp",
|
|
5
|
+
"description": "Sample audit bundle with 3 receipts from a protect-mcp session.",
|
|
6
|
+
"verification": {
|
|
7
|
+
"signing_keys": [
|
|
8
|
+
{
|
|
9
|
+
"kty": "OKP",
|
|
10
|
+
"crv": "Ed25519",
|
|
11
|
+
"kid": "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k",
|
|
12
|
+
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
|
|
13
|
+
"use": "sig"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"receipts": [
|
|
18
|
+
{
|
|
19
|
+
"v": 2,
|
|
20
|
+
"type": "decision_receipt",
|
|
21
|
+
"algorithm": "ed25519",
|
|
22
|
+
"kid": "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k",
|
|
23
|
+
"issuer": "sb:test",
|
|
24
|
+
"issued_at": "2026-01-01T00:00:00Z",
|
|
25
|
+
"payload": {
|
|
26
|
+
"decision": "allow",
|
|
27
|
+
"policy_digest": "sha256:abcdef0123456789",
|
|
28
|
+
"scope": "my-service",
|
|
29
|
+
"tool": "read_database",
|
|
30
|
+
"tier": "signed-known",
|
|
31
|
+
"mode": "shadow",
|
|
32
|
+
"reason_code": "policy_match",
|
|
33
|
+
"request_id": "req_test_001"
|
|
34
|
+
},
|
|
35
|
+
"signature": "324a966f8d4e6652e2270311c9682157d5adc01f1f019d84b24a1125220869a5c5a0fc0096ed3afffaa66ac36cfbbd97e60d9c5f7ad632a2cf11c45c2c50fd0d"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"v": 2,
|
|
39
|
+
"type": "gateway_restraint",
|
|
40
|
+
"algorithm": "ed25519",
|
|
41
|
+
"kid": "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k",
|
|
42
|
+
"issuer": "sb:protect",
|
|
43
|
+
"issued_at": "2026-01-01T00:01:00Z",
|
|
44
|
+
"payload": {
|
|
45
|
+
"tool": "delete_user",
|
|
46
|
+
"decision": "deny",
|
|
47
|
+
"reason_code": "tier_insufficient",
|
|
48
|
+
"policy_digest": "sha256:abcdef0123456789",
|
|
49
|
+
"agent_id": "sb:agent:test-bot",
|
|
50
|
+
"tier": "unknown",
|
|
51
|
+
"mode": "enforce"
|
|
52
|
+
},
|
|
53
|
+
"signature": "e0acddfd57dac1d1cd1a7b48d4554c81cede6bf44aa40f97ed5cbf8825e4157ec1fb44527b0b2cc17b24e5853b2190e02e5c7a826c3919592e693f09c04fac0e"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"v": 2,
|
|
57
|
+
"type": "trust_ticket",
|
|
58
|
+
"algorithm": "ed25519",
|
|
59
|
+
"kid": "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k",
|
|
60
|
+
"issuer": "sb:trust-authority",
|
|
61
|
+
"issued_at": "2026-01-15T12:00:00Z",
|
|
62
|
+
"payload": {
|
|
63
|
+
"tier": "evidenced",
|
|
64
|
+
"scope": "production",
|
|
65
|
+
"expires_at": "2026-02-01T00:00:00Z",
|
|
66
|
+
"agent_id": "sb:agent:verified-bot",
|
|
67
|
+
"manifest_hash": "sha256:manifest_hash_example",
|
|
68
|
+
"evidence_summary": {
|
|
69
|
+
"receipt_count": 150,
|
|
70
|
+
"epoch_span": 30,
|
|
71
|
+
"issuer_count": 5
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"signature": "c4e5b0048449d0f6757877410d3066f285dc07d1f73ec1e4f6222ec2f3a95a3953db3f3c9a3079c7a7cef837e36d522791a095884633f52d42876b1b25ae6600"
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"_note": "Verify with: npx @veritasacta/verify sample-bundle.json --bundle"
|
|
78
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"v": 2,
|
|
3
|
+
"type": "decision_receipt",
|
|
4
|
+
"algorithm": "ed25519",
|
|
5
|
+
"kid": "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k",
|
|
6
|
+
"issuer": "sb:test",
|
|
7
|
+
"issued_at": "2026-01-01T00:00:00Z",
|
|
8
|
+
"payload": {
|
|
9
|
+
"decision": "allow",
|
|
10
|
+
"policy_digest": "sha256:abcdef0123456789",
|
|
11
|
+
"scope": "my-service",
|
|
12
|
+
"tool": "read_database",
|
|
13
|
+
"tier": "signed-known",
|
|
14
|
+
"mode": "shadow",
|
|
15
|
+
"reason_code": "policy_match",
|
|
16
|
+
"request_id": "req_test_001"
|
|
17
|
+
},
|
|
18
|
+
"signature": "324a966f8d4e6652e2270311c9682157d5adc01f1f019d84b24a1125220869a5c5a0fc0096ed3afffaa66ac36cfbbd97e60d9c5f7ad632a2cf11c45c2c50fd0d"
|
|
19
|
+
}
|
package/server.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import {
|
|
10
|
+
verifyArtifact,
|
|
11
|
+
canonicalHash,
|
|
12
|
+
bytesToHex,
|
|
13
|
+
base64urlToBytes,
|
|
14
|
+
} from '@veritasacta/artifacts';
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
18
|
+
function readJsonInput(path, raw) {
|
|
19
|
+
if (raw && raw.trim()) return JSON.parse(raw);
|
|
20
|
+
if (path && path.trim()) return JSON.parse(readFileSync(path, 'utf-8'));
|
|
21
|
+
throw new Error('Provide either raw JSON input or a file path.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isPassportEnvelope(obj) {
|
|
25
|
+
return obj && typeof obj === 'object'
|
|
26
|
+
&& obj.payload && typeof obj.payload === 'object'
|
|
27
|
+
&& obj.signature && typeof obj.signature === 'object'
|
|
28
|
+
&& typeof obj.signature.sig === 'string';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function convertPassportToV1(envelope) {
|
|
32
|
+
return {
|
|
33
|
+
artifact: { ...envelope.payload, signature: envelope.signature.sig },
|
|
34
|
+
kid: envelope.signature.kid || null,
|
|
35
|
+
format: 'passport',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function deriveEmbeddedKey(artifact) {
|
|
40
|
+
const payload = artifact?.payload || artifact;
|
|
41
|
+
if (payload?.public_key && typeof payload.public_key === 'string' && payload.public_key.length === 64) {
|
|
42
|
+
return payload.public_key;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function formatArtifact(artifact) {
|
|
48
|
+
if (isPassportEnvelope(artifact)) return 'passport';
|
|
49
|
+
if (artifact?.v === 2) return 'v2';
|
|
50
|
+
return 'v1';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getArtifactCore(artifact) {
|
|
54
|
+
if (isPassportEnvelope(artifact)) {
|
|
55
|
+
return convertPassportToV1(artifact);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
artifact,
|
|
59
|
+
kid: artifact?.kid || null,
|
|
60
|
+
format: formatArtifact(artifact),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function resolveBundleKeyMap(bundle) {
|
|
65
|
+
const keys = bundle?.verification?.signing_keys || [];
|
|
66
|
+
const map = new Map();
|
|
67
|
+
for (const jwk of keys) {
|
|
68
|
+
if (jwk?.kid && jwk?.x) {
|
|
69
|
+
map.set(jwk.kid, bytesToHex(base64urlToBytes(jwk.x)));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return map;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function verifySingle(artifact, publicKeyHex) {
|
|
76
|
+
const core = getArtifactCore(artifact);
|
|
77
|
+
const key = publicKeyHex || deriveEmbeddedKey(artifact);
|
|
78
|
+
if (!key) {
|
|
79
|
+
return {
|
|
80
|
+
valid: false,
|
|
81
|
+
error: 'no_public_key',
|
|
82
|
+
type: artifact?.type || core.artifact?.type || 'unknown',
|
|
83
|
+
format: core.format,
|
|
84
|
+
kid: core.kid,
|
|
85
|
+
issuer: artifact?.issuer || null,
|
|
86
|
+
hash: null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const result = verifyArtifact(core.artifact, key);
|
|
91
|
+
const unsigned = { ...core.artifact };
|
|
92
|
+
delete unsigned.signature;
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
valid: !!result.valid,
|
|
96
|
+
error: result.valid ? null : (result.error || 'invalid_signature'),
|
|
97
|
+
type: artifact?.type || core.artifact?.type || 'unknown',
|
|
98
|
+
format: core.format,
|
|
99
|
+
kid: core.kid,
|
|
100
|
+
issuer: artifact?.issuer || null,
|
|
101
|
+
hash: canonicalHash(unsigned),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function verifyBundle(bundle) {
|
|
106
|
+
if (!bundle?.receipts || !Array.isArray(bundle.receipts)) {
|
|
107
|
+
throw new Error('Invalid bundle: missing receipts array');
|
|
108
|
+
}
|
|
109
|
+
const keyMap = resolveBundleKeyMap(bundle);
|
|
110
|
+
let passed = 0;
|
|
111
|
+
const receipts = bundle.receipts.map((receipt, index) => {
|
|
112
|
+
const key = receipt?.kid ? keyMap.get(receipt.kid) : deriveEmbeddedKey(receipt);
|
|
113
|
+
const result = verifySingle(receipt, key || null);
|
|
114
|
+
if (result.valid) passed += 1;
|
|
115
|
+
return {
|
|
116
|
+
index,
|
|
117
|
+
type: result.type,
|
|
118
|
+
kid: result.kid,
|
|
119
|
+
valid: result.valid,
|
|
120
|
+
error: result.error,
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
valid: passed === bundle.receipts.length,
|
|
125
|
+
total: bundle.receipts.length,
|
|
126
|
+
passed,
|
|
127
|
+
failed: bundle.receipts.length - passed,
|
|
128
|
+
receipts,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function explainArtifact(artifact) {
|
|
133
|
+
const core = getArtifactCore(artifact);
|
|
134
|
+
const payload = artifact?.payload || core.artifact?.payload || core.artifact;
|
|
135
|
+
const payloadKeys = payload && typeof payload === 'object'
|
|
136
|
+
? Object.keys(payload).filter((k) => k !== 'signature').sort()
|
|
137
|
+
: [];
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
type: artifact?.type || core.artifact?.type || 'unknown',
|
|
141
|
+
format: core.format,
|
|
142
|
+
issuer: artifact?.issuer || null,
|
|
143
|
+
kid: core.kid,
|
|
144
|
+
issued_at: artifact?.issued_at || artifact?.timestamp || payload?.issued_at || null,
|
|
145
|
+
payload_keys: payloadKeys,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function loadSelfTestArtifacts() {
|
|
150
|
+
return {
|
|
151
|
+
receipt: JSON.parse(readFileSync(join(__dirname, 'samples', 'sample-receipt.json'), 'utf-8')),
|
|
152
|
+
bundle: JSON.parse(readFileSync(join(__dirname, 'samples', 'sample-bundle.json'), 'utf-8')),
|
|
153
|
+
publicKeyHex: 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function textResult(value) {
|
|
158
|
+
return { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const server = new McpServer({
|
|
162
|
+
name: 'scopeblind-verify',
|
|
163
|
+
version: '0.1.0',
|
|
164
|
+
description: 'Offline verification MCP server for ScopeBlind and Veritas Acta artifacts.',
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
server.tool(
|
|
168
|
+
'self_test',
|
|
169
|
+
'Verify the packaged sample receipt and sample bundle to prove the verifier works offline.',
|
|
170
|
+
{},
|
|
171
|
+
async () => {
|
|
172
|
+
try {
|
|
173
|
+
const { receipt, bundle, publicKeyHex } = loadSelfTestArtifacts();
|
|
174
|
+
const receiptResult = verifySingle(receipt, publicKeyHex);
|
|
175
|
+
const bundleResult = verifyBundle(bundle);
|
|
176
|
+
return textResult({
|
|
177
|
+
ok: receiptResult.valid && bundleResult.valid,
|
|
178
|
+
receipt: receiptResult,
|
|
179
|
+
bundle: {
|
|
180
|
+
valid: bundleResult.valid,
|
|
181
|
+
total: bundleResult.total,
|
|
182
|
+
passed: bundleResult.passed,
|
|
183
|
+
failed: bundleResult.failed,
|
|
184
|
+
},
|
|
185
|
+
note: 'No ScopeBlind servers were contacted.',
|
|
186
|
+
});
|
|
187
|
+
} catch (error) {
|
|
188
|
+
return textResult({ ok: false, error: error.message });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
server.tool(
|
|
194
|
+
'verify_receipt',
|
|
195
|
+
'Verify a single signed artifact or receipt using an explicit public key or any embedded public key.',
|
|
196
|
+
{
|
|
197
|
+
artifact_json: z.string().optional().describe('Raw JSON artifact string.'),
|
|
198
|
+
path: z.string().optional().describe('Path to a local JSON artifact file.'),
|
|
199
|
+
public_key_hex: z.string().optional().describe('Optional Ed25519 public key hex (64 bytes as hex).'),
|
|
200
|
+
},
|
|
201
|
+
async (args) => {
|
|
202
|
+
try {
|
|
203
|
+
const artifact = readJsonInput(args.path, args.artifact_json);
|
|
204
|
+
return textResult(verifySingle(artifact, args.public_key_hex || null));
|
|
205
|
+
} catch (error) {
|
|
206
|
+
return textResult({ ok: false, error: error.message });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
server.tool(
|
|
212
|
+
'verify_bundle',
|
|
213
|
+
'Verify a ScopeBlind audit bundle offline using the embedded verification keys.',
|
|
214
|
+
{
|
|
215
|
+
bundle_json: z.string().optional().describe('Raw JSON bundle string.'),
|
|
216
|
+
path: z.string().optional().describe('Path to a local JSON bundle file.'),
|
|
217
|
+
},
|
|
218
|
+
async (args) => {
|
|
219
|
+
try {
|
|
220
|
+
const bundle = readJsonInput(args.path, args.bundle_json);
|
|
221
|
+
return textResult(verifyBundle(bundle));
|
|
222
|
+
} catch (error) {
|
|
223
|
+
return textResult({ ok: false, error: error.message });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
server.tool(
|
|
229
|
+
'explain_artifact',
|
|
230
|
+
'Explain the format and top-level contents of a signed artifact without requiring a verification key.',
|
|
231
|
+
{
|
|
232
|
+
artifact_json: z.string().optional().describe('Raw JSON artifact string.'),
|
|
233
|
+
path: z.string().optional().describe('Path to a local JSON artifact file.'),
|
|
234
|
+
},
|
|
235
|
+
async (args) => {
|
|
236
|
+
try {
|
|
237
|
+
const artifact = readJsonInput(args.path, args.artifact_json);
|
|
238
|
+
return textResult(explainArtifact(artifact));
|
|
239
|
+
} catch (error) {
|
|
240
|
+
return textResult({ ok: false, error: error.message });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const transport = new StdioServerTransport();
|
|
246
|
+
await server.connect(transport);
|
package/server.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.tomjwxf/verify-mcp",
|
|
4
|
+
"description": "Offline verification MCP server for ScopeBlind and Veritas Acta artifacts, including receipts, bundles, manifests, and trust tickets.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/tomjwxf/scopeblind-gateway",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "0.1.0",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "@scopeblind/verify-mcp",
|
|
14
|
+
"version": "0.1.0",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
},
|
|
18
|
+
"runtime": "node",
|
|
19
|
+
"runtimeArgs": []
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|