tersign 0.4.4 → 0.4.5
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/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +94 -39
- package/package.json +1 -1
package/dist/mcp/server.d.ts
CHANGED
|
@@ -8,6 +8,6 @@ export declare function envDeps(env?: Record<string, string | undefined>): McpDe
|
|
|
8
8
|
* every client; mcp.test.ts pins it against package.json so a release bump can't drift it. */
|
|
9
9
|
export declare const MCP_SERVER_IDENTITY: {
|
|
10
10
|
readonly name: "tersign";
|
|
11
|
-
readonly version: "0.4.
|
|
11
|
+
readonly version: "0.4.5";
|
|
12
12
|
};
|
|
13
13
|
export declare function buildServer(deps: McpDeps): McpServer;
|
package/dist/mcp/server.js
CHANGED
|
@@ -45,30 +45,47 @@ function json(value) {
|
|
|
45
45
|
}
|
|
46
46
|
/** MUST match package.json name/version — the MCP handshake self-reports this identity to
|
|
47
47
|
* every client; mcp.test.ts pins it against package.json so a release bump can't drift it. */
|
|
48
|
-
export const MCP_SERVER_IDENTITY = { name: 'tersign', version: '0.4.
|
|
48
|
+
export const MCP_SERVER_IDENTITY = { name: 'tersign', version: '0.4.5' };
|
|
49
49
|
export function buildServer(deps) {
|
|
50
50
|
const server = new McpServer(MCP_SERVER_IDENTITY);
|
|
51
51
|
server.registerTool('issue_receipt', {
|
|
52
52
|
title: 'Issue signed receipt',
|
|
53
|
-
description: 'Issue an x402 offer-receipt (EIP-712) plus
|
|
53
|
+
description: 'Issue an x402 offer-receipt (EIP-712) plus a Tersign action record for a payment that has ALREADY settled, and counter-sign both into your hash chain when a ledger is configured. ' +
|
|
54
|
+
'Use this for money that moved; use record_disclosure for a non-payment agent action. ' +
|
|
55
|
+
'Side effects: signs with TERSIGN_SELLER_KEY, and performs ONE network write to the ledger when TERSIGN_LEDGER_URL/_API_KEY/_SELLER_ID are set (without them it signs locally and returns an unchained artifact). ' +
|
|
56
|
+
'Returns the signed receipt artifact, its keccak256 canonical digest, and — when chained — the ledger counter-signature and sequence number.',
|
|
54
57
|
inputSchema: {
|
|
55
|
-
network: z.string().describe('CAIP-2, e.g. eip155:8453'),
|
|
56
|
-
resourceUrl: z.string().url(),
|
|
57
|
-
payer: z.string(),
|
|
58
|
-
supplyDescription: z.string(),
|
|
59
|
-
settledAt: z.number().int().optional(),
|
|
60
|
-
txHash: z.string().optional(),
|
|
61
|
-
taxScheme: z
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
network: z.string().describe('settlement network as CAIP-2, e.g. "eip155:8453" for Base mainnet'),
|
|
59
|
+
resourceUrl: z.string().url().describe('absolute URL of the resource that was paid for; appears verbatim in the receipt'),
|
|
60
|
+
payer: z.string().describe('0x address that paid — the party who can later open a dispute against this receipt'),
|
|
61
|
+
supplyDescription: z.string().describe('what was supplied, in the seller\'s own words; the human-readable line an auditor or venue reads'),
|
|
62
|
+
settledAt: z.number().int().optional().describe('unix seconds when settlement occurred; defaults to now. Set it explicitly when back-filling'),
|
|
63
|
+
txHash: z.string().optional().describe('on-chain settlement transaction hash, when one exists; omit for off-chain or fiat settlement'),
|
|
64
|
+
taxScheme: z
|
|
65
|
+
.enum(['none', 'vat', 'gst', 'jct', 'sales'])
|
|
66
|
+
.optional()
|
|
67
|
+
.describe('tax regime the seller is accounting under; recorded, never computed — Tersign does not calculate tax'),
|
|
68
|
+
currency: z.string().optional().describe('settlement currency code, e.g. "USDC" or "USD"'),
|
|
69
|
+
principal: z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe('the party on whose authority the paying agent acted (x402 sense: the buyer who delegated). Omit when a human paid directly'),
|
|
64
73
|
},
|
|
65
74
|
}, async (args) => json(await issueReceiptTool(deps, args)));
|
|
66
75
|
server.registerTool('verify_receipt', {
|
|
67
76
|
title: 'Verify signed receipt',
|
|
68
|
-
description: 'Verify an offer-receipt artifact
|
|
77
|
+
description: 'Verify an offer-receipt artifact: recover the EIP-712 signature and confirm the payload digest binds to it. ' +
|
|
78
|
+
'Fully OFFLINE — no network, no API key, no account; verifying someone else\'s receipt is the intended use. ' +
|
|
79
|
+
'Use this for a receipt (money); use verify_compliance_record for an action record (a non-payment action). ' +
|
|
80
|
+
'Returns { valid, signer, digest } and, when expectedSigner is supplied and does not match, valid:false with the recovered signer so you can see who actually signed.',
|
|
69
81
|
inputSchema: {
|
|
70
|
-
artifact: z
|
|
71
|
-
|
|
82
|
+
artifact: z
|
|
83
|
+
.record(z.unknown())
|
|
84
|
+
.describe('the receipt artifact exactly as issued: { format, payload, signature }. Pass the object, not a JSON string'),
|
|
85
|
+
expectedSigner: z
|
|
86
|
+
.string()
|
|
87
|
+
.optional()
|
|
88
|
+
.describe('0x address the receipt MUST be signed by — obtain it out-of-band, never from the artifact. Omit to recover the signer without enforcing it'),
|
|
72
89
|
},
|
|
73
90
|
}, async ({ artifact, expectedSigner }) => json(await verifyReceiptTool(artifact, expectedSigner)));
|
|
74
91
|
server.registerTool('record_disclosure', {
|
|
@@ -78,27 +95,49 @@ export function buildServer(deps) {
|
|
|
78
95
|
text: z.string().optional().describe('the disclosure text as presented — digested locally, never transmitted'),
|
|
79
96
|
textDigest: z.string().regex(/^0x[0-9a-fA-F]{64}$/).optional().describe('pre-computed digest (wins over text)'),
|
|
80
97
|
medium: z.string().optional().describe("channel: 'chat' | 'api' | 'voice' | 'ui' …"),
|
|
81
|
-
kind: z
|
|
82
|
-
|
|
83
|
-
|
|
98
|
+
kind: z
|
|
99
|
+
.enum(['ai-interaction', 'synthetic-content'])
|
|
100
|
+
.optional()
|
|
101
|
+
.describe("what was disclosed: 'ai-interaction' = the user was told they are talking to an AI; 'synthetic-content' = output was marked machine-generated. Defaults to 'ai-interaction'"),
|
|
102
|
+
agentId: z.string().describe('stable identifier for the disclosing agent — keep it constant across calls so one chain accumulates per agent'),
|
|
103
|
+
resourceUrl: z.string().url().optional().describe('absolute URL of the surface the disclosure was presented on, when there is one'),
|
|
84
104
|
},
|
|
85
105
|
}, async (args) => json(await recordDisclosureTool(deps, args)));
|
|
86
106
|
server.registerTool('verify_compliance_record', {
|
|
87
107
|
title: 'Verify compliance record',
|
|
88
|
-
description: 'Verify
|
|
108
|
+
description: 'Verify a Tersign action record against its attestation: recompute the record\'s canonical digest, confirm the attestation commits to that exact digest, and recover the signature. ' +
|
|
109
|
+
'Fully OFFLINE — no network, no API key, no account. ' +
|
|
110
|
+
'Use this for an action record (a disclosure or other non-payment agent action); use verify_receipt for a payment receipt. ' +
|
|
111
|
+
'PASS proves integrity and internal consistency only. Authorship needs an out-of-band signer address: pass expectedSigner, or the identity is whatever the artifact claims about itself. ' +
|
|
112
|
+
'Returns { valid, signer, digest }; on mismatch, valid:false plus the recovered signer and the recomputed digest.',
|
|
89
113
|
inputSchema: {
|
|
90
|
-
record: z
|
|
91
|
-
|
|
92
|
-
|
|
114
|
+
record: z
|
|
115
|
+
.record(z.unknown())
|
|
116
|
+
.describe('the action record object as issued (ComplianceRecordV1 shape). Pass the object, not a JSON string; any field edit changes the digest and fails verification — which is the point'),
|
|
117
|
+
attestation: z
|
|
118
|
+
.record(z.unknown())
|
|
119
|
+
.describe('the attestation that accompanies the record: the signature over the record digest, as returned alongside it at issuance'),
|
|
120
|
+
expectedSigner: z
|
|
121
|
+
.string()
|
|
122
|
+
.optional()
|
|
123
|
+
.describe('0x address the record MUST be signed by, obtained out-of-band (for the public ledger: https://tersign.ai/v1/ledger). Omit to recover the signer without enforcing it'),
|
|
93
124
|
},
|
|
94
125
|
}, async ({ record, attestation, expectedSigner }) => json(await verifyRecordTool(record, attestation, expectedSigner)));
|
|
95
126
|
server.registerTool('record_refund', {
|
|
96
127
|
title: 'Record refund',
|
|
97
|
-
description: 'Record a refund against
|
|
128
|
+
description: 'Record a refund against an already-chained receipt, as the SELLER. The refund becomes its own counter-signed entry that references the original — nothing is edited or deleted, so the chain stays append-only and both the charge and the refund remain visible. ' +
|
|
129
|
+
'Requires ledger configuration (TERSIGN_LEDGER_URL/_API_KEY/_SELLER_ID) and performs one network write; errors if the original digest is not on your chain. ' +
|
|
130
|
+
'This RECORDS a refund you have already made — it moves no money. ' +
|
|
131
|
+
'Returns the refund record, its digest, the ledger counter-signature and sequence number.',
|
|
98
132
|
inputSchema: {
|
|
99
|
-
originalDigest: z
|
|
100
|
-
|
|
101
|
-
|
|
133
|
+
originalDigest: z
|
|
134
|
+
.string()
|
|
135
|
+
.regex(/^0x[0-9a-fA-F]{64}$/)
|
|
136
|
+
.describe('0x-prefixed keccak256 digest of the receipt being refunded — the digest returned by issue_receipt, and it must already exist on your chain'),
|
|
137
|
+
amount: z
|
|
138
|
+
.string()
|
|
139
|
+
.describe('refunded amount as a decimal STRING in the original settlement currency, e.g. "12.50". A string, not a number, so no precision is lost. Partial refunds are allowed'),
|
|
140
|
+
reason: z.string().describe('why the refund was issued, in your own words; recorded verbatim for whoever reads the chain later'),
|
|
102
141
|
},
|
|
103
142
|
}, async ({ originalDigest, amount, reason }) => json(await recordRefundTool(deps, originalDigest, amount, reason)));
|
|
104
143
|
const digestSchema = z.string().regex(/^0x[0-9a-fA-F]{64}$/);
|
|
@@ -106,9 +145,13 @@ export function buildServer(deps) {
|
|
|
106
145
|
title: 'Open dispute',
|
|
107
146
|
description: 'Open an objective dispute against a counter-signed receipt as the PAYER (the configured key must be the receipt payer). Reasons: not_delivered, wrong_content, duplicate_charge. Contested non-mechanical claims escalate to the arbiter; duplicate_charge is decided instantly from ledger arithmetic.',
|
|
108
147
|
inputSchema: {
|
|
109
|
-
receiptDigest: digestSchema,
|
|
110
|
-
reason: z
|
|
111
|
-
|
|
148
|
+
receiptDigest: digestSchema.describe('0x-prefixed keccak256 digest of the counter-signed receipt being disputed'),
|
|
149
|
+
reason: z
|
|
150
|
+
.enum(['not_delivered', 'wrong_content', 'duplicate_charge'])
|
|
151
|
+
.describe("grounds: 'not_delivered' nothing arrived · 'wrong_content' delivered but not what was bought · 'duplicate_charge' the same supply was billed twice (decided mechanically from the chain, no arbiter)"),
|
|
152
|
+
claimAmount: z
|
|
153
|
+
.string()
|
|
154
|
+
.describe('amount claimed back, as a decimal STRING in the receipt\'s settlement currency, e.g. "12.50"; must not exceed the receipt amount'),
|
|
112
155
|
statement: z.string().optional().describe('for humans reading the record — never an adjudication input'),
|
|
113
156
|
},
|
|
114
157
|
}, async ({ receiptDigest, reason, claimAmount, statement }) => json(await openDisputeTool(deps, { receiptDigest: receiptDigest, reason, claimAmount, statement })));
|
|
@@ -116,16 +159,21 @@ export function buildServer(deps) {
|
|
|
116
159
|
title: 'Submit dispute evidence',
|
|
117
160
|
description: 'Submit signed evidence to an open dispute. Claimant evidence must be signed by the payer key; respondent evidence additionally requires the seller API key (TERSIGN_LEDGER_API_KEY).',
|
|
118
161
|
inputSchema: {
|
|
119
|
-
disputeDigest: digestSchema,
|
|
120
|
-
role: z
|
|
162
|
+
disputeDigest: digestSchema.describe('0x-prefixed digest of the open dispute, as returned by open_dispute'),
|
|
163
|
+
role: z
|
|
164
|
+
.enum(['claimant', 'respondent'])
|
|
165
|
+
.describe("which side you are filing as: 'claimant' = the payer who opened it (payer key) · 'respondent' = the seller answering it (also needs TERSIGN_LEDGER_API_KEY)"),
|
|
121
166
|
artifacts: z
|
|
122
167
|
.array(z.object({
|
|
123
|
-
kind: z
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
168
|
+
kind: z
|
|
169
|
+
.enum(['content-digest', 'delivery-attestation', 'payment-proof', 'transcript'])
|
|
170
|
+
.describe('what this artifact is; the adjudicator treats each kind differently'),
|
|
171
|
+
digest: digestSchema.describe('0x-prefixed keccak256 digest of the artifact. Only the DIGEST is submitted — the content itself never leaves your side'),
|
|
172
|
+
at: z.number().int().optional().describe('unix seconds the artifact was produced; supply it when timing is part of your argument'),
|
|
173
|
+
note: z.string().optional().describe('short human-readable label for whoever reads the record; never an adjudication input'),
|
|
127
174
|
}))
|
|
128
|
-
.min(1)
|
|
175
|
+
.min(1)
|
|
176
|
+
.describe('at least one evidence reference; submit every artifact you want considered in a single call'),
|
|
129
177
|
},
|
|
130
178
|
}, async ({ disputeDigest, role, artifacts }) => json(await submitEvidenceTool(deps, {
|
|
131
179
|
disputeDigest: disputeDigest,
|
|
@@ -134,13 +182,20 @@ export function buildServer(deps) {
|
|
|
134
182
|
})));
|
|
135
183
|
server.registerTool('adjudicate_dispute', {
|
|
136
184
|
title: 'Adjudicate dispute',
|
|
137
|
-
description: 'Trigger deterministic adjudication of an open dispute
|
|
138
|
-
|
|
185
|
+
description: 'Trigger deterministic adjudication of an open dispute. The v0 rulebook is public and the verdict is recomputable by anyone from the chain — no discretion, no model in the loop. ' +
|
|
186
|
+
'Side effects: writes a verdict entry, and a refund verdict automatically creates the corresponding refund record. Adjudicating twice is not meaningful; the first verdict stands. ' +
|
|
187
|
+
'Returns the verdict, the rationale naming the rule applied, and the ledger signature over both.',
|
|
188
|
+
inputSchema: {
|
|
189
|
+
disputeDigest: digestSchema.describe('0x-prefixed digest of the open dispute to adjudicate, as returned by open_dispute'),
|
|
190
|
+
},
|
|
139
191
|
}, async ({ disputeDigest }) => json(await adjudicateDisputeTool(deps, disputeDigest)));
|
|
140
192
|
server.registerTool('get_dispute', {
|
|
141
193
|
title: 'Get dispute record',
|
|
142
|
-
description: 'Fetch a dispute
|
|
143
|
-
|
|
194
|
+
description: 'Fetch a dispute in full: its state, both sides\' evidence references, the verdict and rationale once adjudicated, and the ledger signature over the record. ' +
|
|
195
|
+
'Read-only — one network read, no key required, and safe to poll while a dispute is open.',
|
|
196
|
+
inputSchema: {
|
|
197
|
+
disputeDigest: digestSchema.describe('0x-prefixed digest of the dispute to fetch, as returned by open_dispute'),
|
|
198
|
+
},
|
|
144
199
|
}, async ({ disputeDigest }) => json(await getDisputeTool(deps, disputeDigest)));
|
|
145
200
|
return server;
|
|
146
201
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tersign",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Tersign \u2014 the evidence layer for the agent economy. Counter-signed receipts, agent action records, idempotency enforcement, refunds, disputes, and jury-ready evidence envelopes for x402/agent-commerce sellers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|