@aura-labs-ai/scout 0.2.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 +237 -0
- package/bin/scout-cli.js +325 -0
- package/examples/basic-purchase.js +112 -0
- package/examples/protocol-integration.js +294 -0
- package/package.json +63 -0
- package/src/activity.js +420 -0
- package/src/ap2/mandates.js +366 -0
- package/src/client.js +217 -0
- package/src/errors.js +66 -0
- package/src/index.js +655 -0
- package/src/intent-session.js +233 -0
- package/src/key-manager.js +204 -0
- package/src/key-manager.test.js +293 -0
- package/src/mcp/client.js +458 -0
- package/src/session.js +603 -0
- package/src/tap/visa.js +345 -0
- package/src/tests/README.md +157 -0
- package/src/tests/ap2-mandates.test.js +413 -0
- package/src/tests/intent-session.test.js +292 -0
- package/src/tests/mcp-client.test.js +224 -0
- package/src/tests/ping.test.js +282 -0
- package/src/tests/run-protocol-tests.js +57 -0
- package/src/tests/scenarios/ap2-scenarios.test.js +422 -0
- package/src/tests/scenarios/index.js +60 -0
- package/src/tests/scenarios/integration-scenarios.test.js +514 -0
- package/src/tests/scenarios/mcp-scenarios.test.js +419 -0
- package/src/tests/scenarios/tap-scenarios.test.js +461 -0
- package/src/tests/visa-tap.test.js +376 -0
package/src/tap/visa.js
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visa TAP (Trusted Agent Protocol) for AURA Scout SDK
|
|
3
|
+
*
|
|
4
|
+
* Implements Visa's Trusted Agent Protocol for agent identity
|
|
5
|
+
* verification in commerce transactions.
|
|
6
|
+
*
|
|
7
|
+
* Key Features:
|
|
8
|
+
* - Agent registration with Visa directory
|
|
9
|
+
* - HTTP message signing for transaction requests
|
|
10
|
+
* - Identity verification for merchants/payment networks
|
|
11
|
+
*
|
|
12
|
+
* @see https://usa.visa.com/about-visa/newsroom/press-releases.releaseId.21716.html
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createHash, createSign, createVerify, generateKeyPairSync, randomBytes } from 'crypto';
|
|
16
|
+
|
|
17
|
+
// TAP Registry URL (would be Visa's in production)
|
|
18
|
+
const TAP_REGISTRY_URL = process.env.TAP_REGISTRY_URL || 'https://tap.visa.com/v1';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Visa TAP - Trusted Agent Protocol implementation
|
|
22
|
+
*/
|
|
23
|
+
export class VisaTAP {
|
|
24
|
+
/**
|
|
25
|
+
* Generate a new agent key pair
|
|
26
|
+
*
|
|
27
|
+
* @returns {Object} Public and private keys
|
|
28
|
+
*/
|
|
29
|
+
static generateKeyPair() {
|
|
30
|
+
const { publicKey, privateKey } = generateKeyPairSync('ed25519', {
|
|
31
|
+
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
32
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Also generate key ID
|
|
36
|
+
const keyId = createHash('sha256')
|
|
37
|
+
.update(publicKey)
|
|
38
|
+
.digest('hex')
|
|
39
|
+
.substring(0, 16);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
keyId,
|
|
43
|
+
publicKey,
|
|
44
|
+
privateKey,
|
|
45
|
+
algorithm: 'ed25519',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Register an agent with the TAP directory
|
|
51
|
+
*
|
|
52
|
+
* @param {Object} params
|
|
53
|
+
* @param {string} params.agentId - Unique agent identifier
|
|
54
|
+
* @param {string} params.publicKey - Agent's public key (PEM format)
|
|
55
|
+
* @param {Object} params.metadata - Agent metadata
|
|
56
|
+
* @returns {Object} Registration response with TAP ID
|
|
57
|
+
*/
|
|
58
|
+
static async register({
|
|
59
|
+
agentId,
|
|
60
|
+
publicKey,
|
|
61
|
+
metadata = {},
|
|
62
|
+
}) {
|
|
63
|
+
const registration = {
|
|
64
|
+
agentId,
|
|
65
|
+
publicKey,
|
|
66
|
+
metadata: {
|
|
67
|
+
name: metadata.name || 'AURA Agent',
|
|
68
|
+
operator: metadata.operator || 'AURA Labs',
|
|
69
|
+
capabilities: metadata.capabilities || ['shopping', 'payments'],
|
|
70
|
+
version: metadata.version || '1.0',
|
|
71
|
+
registeredAt: new Date().toISOString(),
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// In production, this would POST to Visa's TAP registry
|
|
76
|
+
// For development, simulate registration
|
|
77
|
+
if (process.env.NODE_ENV === 'production') {
|
|
78
|
+
const response = await fetch(`${TAP_REGISTRY_URL}/agents/register`, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: { 'Content-Type': 'application/json' },
|
|
81
|
+
body: JSON.stringify(registration),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
throw new TAPError('Registration failed', await response.text());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return response.json();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Development mode - simulate registration
|
|
92
|
+
const tapId = `tap_${agentId}_${randomBytes(8).toString('hex')}`;
|
|
93
|
+
return {
|
|
94
|
+
tapId,
|
|
95
|
+
agentId,
|
|
96
|
+
status: 'active',
|
|
97
|
+
registeredAt: new Date().toISOString(),
|
|
98
|
+
expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(), // 1 year
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Sign an HTTP request for TAP verification
|
|
104
|
+
*
|
|
105
|
+
* Creates a signature that proves the request came from
|
|
106
|
+
* a registered agent acting on behalf of a user.
|
|
107
|
+
*
|
|
108
|
+
* @param {Object} request - Request to sign
|
|
109
|
+
* @param {string} request.method - HTTP method
|
|
110
|
+
* @param {string} request.url - Request URL
|
|
111
|
+
* @param {Object} request.headers - Request headers
|
|
112
|
+
* @param {Object} request.body - Request body
|
|
113
|
+
* @param {Object} credentials - TAP credentials
|
|
114
|
+
* @param {string} credentials.tapId - TAP registration ID
|
|
115
|
+
* @param {string} credentials.privateKey - Agent's private key
|
|
116
|
+
* @param {string} credentials.keyId - Key identifier
|
|
117
|
+
*/
|
|
118
|
+
static async signRequest(request, credentials) {
|
|
119
|
+
const { method, url, headers = {}, body } = request;
|
|
120
|
+
const { tapId, privateKey, keyId } = credentials;
|
|
121
|
+
|
|
122
|
+
// Create signature input
|
|
123
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
124
|
+
const nonce = randomBytes(16).toString('hex');
|
|
125
|
+
|
|
126
|
+
// Build signature base string (HTTP Message Signatures spec)
|
|
127
|
+
const signatureInput = this.#buildSignatureInput({
|
|
128
|
+
method,
|
|
129
|
+
url,
|
|
130
|
+
headers,
|
|
131
|
+
body,
|
|
132
|
+
timestamp,
|
|
133
|
+
nonce,
|
|
134
|
+
tapId,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Sign the input
|
|
138
|
+
const signature = this.#sign(signatureInput, privateKey);
|
|
139
|
+
|
|
140
|
+
// Build signature header
|
|
141
|
+
const signatureHeader = this.#buildSignatureHeader({
|
|
142
|
+
keyId,
|
|
143
|
+
algorithm: 'ed25519',
|
|
144
|
+
timestamp,
|
|
145
|
+
nonce,
|
|
146
|
+
signature,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Return signed request with TAP headers
|
|
150
|
+
return {
|
|
151
|
+
...request,
|
|
152
|
+
headers: {
|
|
153
|
+
...headers,
|
|
154
|
+
'X-TAP-Agent-Id': tapId,
|
|
155
|
+
'X-TAP-Timestamp': timestamp.toString(),
|
|
156
|
+
'X-TAP-Nonce': nonce,
|
|
157
|
+
'Signature': signatureHeader,
|
|
158
|
+
'Signature-Input': `sig=("@method" "@path" "@authority" "x-tap-agent-id" "x-tap-timestamp" "x-tap-nonce");keyid="${keyId}";alg="ed25519";created=${timestamp}`,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Verify a TAP-signed request
|
|
165
|
+
*
|
|
166
|
+
* Used by merchants/payment networks to verify agent identity.
|
|
167
|
+
*
|
|
168
|
+
* @param {Object} request - Request to verify
|
|
169
|
+
* @param {Function} publicKeyLookup - Function to get public key from TAP registry
|
|
170
|
+
*/
|
|
171
|
+
static async verifyRequest(request, publicKeyLookup) {
|
|
172
|
+
const tapId = request.headers['x-tap-agent-id'];
|
|
173
|
+
const timestamp = parseInt(request.headers['x-tap-timestamp']);
|
|
174
|
+
const nonce = request.headers['x-tap-nonce'];
|
|
175
|
+
const signatureHeader = request.headers['signature'];
|
|
176
|
+
|
|
177
|
+
if (!tapId || !timestamp || !nonce || !signatureHeader) {
|
|
178
|
+
return { valid: false, error: 'Missing TAP headers' };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Check timestamp freshness (within 5 minutes)
|
|
182
|
+
const now = Math.floor(Date.now() / 1000);
|
|
183
|
+
if (Math.abs(now - timestamp) > 300) {
|
|
184
|
+
return { valid: false, error: 'Request timestamp too old or in future' };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Look up public key from TAP registry
|
|
188
|
+
let publicKey;
|
|
189
|
+
try {
|
|
190
|
+
publicKey = await publicKeyLookup(tapId);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
return { valid: false, error: 'Agent not found in TAP registry' };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Parse signature header
|
|
196
|
+
const signature = this.#parseSignatureHeader(signatureHeader);
|
|
197
|
+
|
|
198
|
+
// Rebuild signature input
|
|
199
|
+
const signatureInput = this.#buildSignatureInput({
|
|
200
|
+
method: request.method,
|
|
201
|
+
url: request.url,
|
|
202
|
+
headers: request.headers,
|
|
203
|
+
body: request.body,
|
|
204
|
+
timestamp,
|
|
205
|
+
nonce,
|
|
206
|
+
tapId,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Verify signature
|
|
210
|
+
try {
|
|
211
|
+
const isValid = this.#verify(signatureInput, signature, publicKey);
|
|
212
|
+
return {
|
|
213
|
+
valid: isValid,
|
|
214
|
+
agentId: tapId,
|
|
215
|
+
timestamp: new Date(timestamp * 1000).toISOString(),
|
|
216
|
+
};
|
|
217
|
+
} catch (error) {
|
|
218
|
+
return { valid: false, error: error.message };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Create TAP credentials object for use in transactions
|
|
224
|
+
*/
|
|
225
|
+
static createCredentials({ tapId, keyPair }) {
|
|
226
|
+
return {
|
|
227
|
+
tapId,
|
|
228
|
+
privateKey: keyPair.privateKey,
|
|
229
|
+
publicKey: keyPair.publicKey,
|
|
230
|
+
keyId: keyPair.keyId,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Rotate agent keys (create new key pair and update registry)
|
|
236
|
+
*/
|
|
237
|
+
static async rotateKeys(tapId, oldPrivateKey) {
|
|
238
|
+
const newKeyPair = this.generateKeyPair();
|
|
239
|
+
|
|
240
|
+
if (process.env.NODE_ENV === 'production') {
|
|
241
|
+
// Sign the rotation request with old key
|
|
242
|
+
const rotationRequest = {
|
|
243
|
+
tapId,
|
|
244
|
+
newPublicKey: newKeyPair.publicKey,
|
|
245
|
+
timestamp: new Date().toISOString(),
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const signature = this.#sign(
|
|
249
|
+
JSON.stringify(rotationRequest),
|
|
250
|
+
oldPrivateKey
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const response = await fetch(`${TAP_REGISTRY_URL}/agents/${tapId}/keys/rotate`, {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
headers: {
|
|
256
|
+
'Content-Type': 'application/json',
|
|
257
|
+
'X-TAP-Signature': signature,
|
|
258
|
+
},
|
|
259
|
+
body: JSON.stringify(rotationRequest),
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
if (!response.ok) {
|
|
263
|
+
throw new TAPError('Key rotation failed', await response.text());
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return newKeyPair;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// =========================================================================
|
|
271
|
+
// Private Methods
|
|
272
|
+
// =========================================================================
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Build signature input string per HTTP Message Signatures spec
|
|
276
|
+
*/
|
|
277
|
+
static #buildSignatureInput({ method, url, headers, body, timestamp, nonce, tapId }) {
|
|
278
|
+
const urlObj = new URL(url, 'https://example.com');
|
|
279
|
+
|
|
280
|
+
const components = [
|
|
281
|
+
`"@method": ${method.toUpperCase()}`,
|
|
282
|
+
`"@path": ${urlObj.pathname}`,
|
|
283
|
+
`"@authority": ${urlObj.host}`,
|
|
284
|
+
`"x-tap-agent-id": ${tapId}`,
|
|
285
|
+
`"x-tap-timestamp": ${timestamp}`,
|
|
286
|
+
`"x-tap-nonce": ${nonce}`,
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
// Include content-digest for requests with body
|
|
290
|
+
if (body) {
|
|
291
|
+
const bodyHash = createHash('sha256')
|
|
292
|
+
.update(typeof body === 'string' ? body : JSON.stringify(body))
|
|
293
|
+
.digest('base64');
|
|
294
|
+
components.push(`"content-digest": sha-256=:${bodyHash}:`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return components.join('\n');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Build signature header value
|
|
302
|
+
*/
|
|
303
|
+
static #buildSignatureHeader({ keyId, algorithm, timestamp, nonce, signature }) {
|
|
304
|
+
return `sig=:${signature}:`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Parse signature from header
|
|
309
|
+
*/
|
|
310
|
+
static #parseSignatureHeader(header) {
|
|
311
|
+
const match = header.match(/sig=:([^:]+):/);
|
|
312
|
+
return match ? match[1] : null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Sign data with private key
|
|
317
|
+
*/
|
|
318
|
+
static #sign(data, privateKey) {
|
|
319
|
+
const signer = createSign('sha256');
|
|
320
|
+
signer.update(data);
|
|
321
|
+
return signer.sign(privateKey, 'base64');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Verify signature with public key
|
|
326
|
+
*/
|
|
327
|
+
static #verify(data, signature, publicKey) {
|
|
328
|
+
const verifier = createVerify('sha256');
|
|
329
|
+
verifier.update(data);
|
|
330
|
+
return verifier.verify(publicKey, signature, 'base64');
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* TAP Protocol Error
|
|
336
|
+
*/
|
|
337
|
+
export class TAPError extends Error {
|
|
338
|
+
constructor(message, details) {
|
|
339
|
+
super(message);
|
|
340
|
+
this.name = 'TAPError';
|
|
341
|
+
this.details = details;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export default VisaTAP;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Scout SDK Tests
|
|
2
|
+
|
|
3
|
+
This directory contains tests for the AURA Scout SDK protocol implementations.
|
|
4
|
+
|
|
5
|
+
## Test Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
tests/
|
|
9
|
+
├── mcp-client.test.js # Unit tests for MCP Client
|
|
10
|
+
├── ap2-mandates.test.js # Unit tests for AP2 Mandates
|
|
11
|
+
├── visa-tap.test.js # Unit tests for Visa TAP
|
|
12
|
+
├── run-protocol-tests.js # Runner for unit tests
|
|
13
|
+
└── scenarios/ # Real-world scenario tests
|
|
14
|
+
├── index.js # Scenario test runner
|
|
15
|
+
├── ap2-scenarios.test.js
|
|
16
|
+
├── tap-scenarios.test.js
|
|
17
|
+
├── mcp-scenarios.test.js
|
|
18
|
+
└── integration-scenarios.test.js
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Running Tests
|
|
22
|
+
|
|
23
|
+
### All Tests
|
|
24
|
+
```bash
|
|
25
|
+
npm test
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Protocol Unit Tests
|
|
29
|
+
```bash
|
|
30
|
+
npm run test:protocols
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Scenario Tests
|
|
34
|
+
```bash
|
|
35
|
+
# All scenarios
|
|
36
|
+
npm run test:scenarios
|
|
37
|
+
|
|
38
|
+
# Individual protocol scenarios
|
|
39
|
+
npm run test:ap2
|
|
40
|
+
npm run test:tap
|
|
41
|
+
npm run test:mcp
|
|
42
|
+
npm run test:integration
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Direct Node.js Execution
|
|
46
|
+
```bash
|
|
47
|
+
node --test src/tests/scenarios/ap2-scenarios.test.js
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Test Categories
|
|
51
|
+
|
|
52
|
+
### Unit Tests (`*.test.js`)
|
|
53
|
+
Basic functionality tests for each protocol implementation:
|
|
54
|
+
- Function inputs/outputs
|
|
55
|
+
- Data structures
|
|
56
|
+
- Error handling
|
|
57
|
+
- API compliance
|
|
58
|
+
|
|
59
|
+
### Scenario Tests (`scenarios/*.test.js`)
|
|
60
|
+
Real-world usage scenario tests:
|
|
61
|
+
|
|
62
|
+
#### AP2 Scenarios (`ap2-scenarios.test.js`)
|
|
63
|
+
- Complete shopping flow (Intent → Cart → Payment)
|
|
64
|
+
- Budget limit enforcement
|
|
65
|
+
- Category restrictions
|
|
66
|
+
- Merchant allowlist/blocklist
|
|
67
|
+
- Time-limited authorization
|
|
68
|
+
- Currency handling
|
|
69
|
+
- Multiple constraint violations
|
|
70
|
+
- Signature verification
|
|
71
|
+
|
|
72
|
+
#### TAP Scenarios (`tap-scenarios.test.js`)
|
|
73
|
+
- Agent registration flow
|
|
74
|
+
- Payment request signing
|
|
75
|
+
- Request verification by merchants
|
|
76
|
+
- Multiple agent identities
|
|
77
|
+
- Key rotation
|
|
78
|
+
- Replay attack protection
|
|
79
|
+
- Different HTTP request types
|
|
80
|
+
- Error handling
|
|
81
|
+
|
|
82
|
+
#### MCP Scenarios (`mcp-scenarios.test.js`)
|
|
83
|
+
- Client initialization
|
|
84
|
+
- Tool discovery
|
|
85
|
+
- Server connection management
|
|
86
|
+
- Event handling
|
|
87
|
+
- Context aggregation
|
|
88
|
+
- Protocol compliance
|
|
89
|
+
- Multiple server support
|
|
90
|
+
- AURA integration patterns
|
|
91
|
+
|
|
92
|
+
#### Integration Scenarios (`integration-scenarios.test.js`)
|
|
93
|
+
- Complete agentic shopping flow (MCP + AP2 + TAP)
|
|
94
|
+
- Multi-offer comparison with constraints
|
|
95
|
+
- Payment network verification
|
|
96
|
+
- MCP context enriching intent
|
|
97
|
+
- Error recovery in protocol chain
|
|
98
|
+
- Time-sensitive transactions
|
|
99
|
+
- Agent capabilities advertisement
|
|
100
|
+
|
|
101
|
+
## Test on Railway (Remote)
|
|
102
|
+
|
|
103
|
+
For environments without local Node.js, tests can be run via the Core API:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Check dev routes enabled
|
|
107
|
+
curl https://aura-labsai-production.up.railway.app/dev/status
|
|
108
|
+
|
|
109
|
+
# Run inline protocol tests
|
|
110
|
+
curl https://aura-labsai-production.up.railway.app/dev/test/protocols
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Writing New Tests
|
|
114
|
+
|
|
115
|
+
### Unit Test Template
|
|
116
|
+
```javascript
|
|
117
|
+
import { test, describe } from 'node:test';
|
|
118
|
+
import assert from 'node:assert';
|
|
119
|
+
import { SomeModule } from '../path/to/module.js';
|
|
120
|
+
|
|
121
|
+
describe('Module Name', () => {
|
|
122
|
+
test('does something specific', () => {
|
|
123
|
+
const result = SomeModule.doSomething();
|
|
124
|
+
assert.strictEqual(result, expected);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Scenario Test Template
|
|
130
|
+
```javascript
|
|
131
|
+
import { test, describe } from 'node:test';
|
|
132
|
+
import assert from 'node:assert';
|
|
133
|
+
|
|
134
|
+
describe('Scenario: Real World Use Case', () => {
|
|
135
|
+
let sharedState;
|
|
136
|
+
|
|
137
|
+
test('Step 1: Setup', async () => {
|
|
138
|
+
sharedState = await setupSomething();
|
|
139
|
+
assert.ok(sharedState);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('Step 2: Action', async () => {
|
|
143
|
+
const result = await doAction(sharedState);
|
|
144
|
+
assert.strictEqual(result.status, 'success');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('Step 3: Verify', () => {
|
|
148
|
+
assert.ok(sharedState.completed);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Protocol References
|
|
154
|
+
|
|
155
|
+
- **MCP**: [Model Context Protocol Specification](https://modelcontextprotocol.io/specification)
|
|
156
|
+
- **AP2**: [Google Agent Payments Protocol](https://ap2-protocol.org/specification/)
|
|
157
|
+
- **Visa TAP**: [Visa Trusted Agent Protocol](https://usa.visa.com/about-visa/newsroom/press-releases.releaseId.21716.html)
|