@cleocode/lafs-protocol 0.5.0 → 1.0.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.
Files changed (38) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +0 -0
  3. package/dist/examples/discovery-server.d.ts +8 -0
  4. package/dist/examples/discovery-server.js +216 -0
  5. package/dist/examples/mcp-lafs-client.d.ts +10 -0
  6. package/dist/examples/mcp-lafs-client.js +427 -0
  7. package/dist/examples/mcp-lafs-server.d.ts +10 -0
  8. package/dist/examples/mcp-lafs-server.js +358 -0
  9. package/dist/schemas/v1/envelope.schema.json +0 -0
  10. package/dist/schemas/v1/error-registry.json +0 -0
  11. package/dist/src/budgetEnforcement.d.ts +84 -0
  12. package/dist/src/budgetEnforcement.js +328 -0
  13. package/dist/src/cli.d.ts +0 -0
  14. package/dist/src/cli.js +0 -0
  15. package/dist/src/conformance.d.ts +0 -0
  16. package/dist/src/conformance.js +0 -0
  17. package/dist/src/discovery.d.ts +127 -0
  18. package/dist/src/discovery.js +304 -0
  19. package/dist/src/errorRegistry.d.ts +0 -0
  20. package/dist/src/errorRegistry.js +0 -0
  21. package/dist/src/flagSemantics.d.ts +0 -0
  22. package/dist/src/flagSemantics.js +0 -0
  23. package/dist/src/index.d.ts +4 -0
  24. package/dist/src/index.js +4 -0
  25. package/dist/src/mcpAdapter.d.ts +28 -0
  26. package/dist/src/mcpAdapter.js +281 -0
  27. package/dist/src/tokenEstimator.d.ts +87 -0
  28. package/dist/src/tokenEstimator.js +238 -0
  29. package/dist/src/types.d.ts +25 -0
  30. package/dist/src/types.js +0 -0
  31. package/dist/src/validateEnvelope.d.ts +0 -0
  32. package/dist/src/validateEnvelope.js +0 -0
  33. package/lafs.md +164 -0
  34. package/package.json +8 -3
  35. package/schemas/v1/context-ledger.schema.json +0 -0
  36. package/schemas/v1/discovery.schema.json +132 -0
  37. package/schemas/v1/envelope.schema.json +0 -0
  38. package/schemas/v1/error-registry.json +0 -0
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
File without changes
@@ -0,0 +1,8 @@
1
+ /**
2
+ * LAFS Discovery Example Server
3
+ *
4
+ * Run with: npx tsx examples/discovery-server.ts
5
+ * Or: npm run build && node dist/examples/discovery-server.js
6
+ */
7
+ declare const app: import("express-serve-static-core").Express;
8
+ export default app;
@@ -0,0 +1,216 @@
1
+ /**
2
+ * LAFS Discovery Example Server
3
+ *
4
+ * Run with: npx tsx examples/discovery-server.ts
5
+ * Or: npm run build && node dist/examples/discovery-server.js
6
+ */
7
+ import express from "express";
8
+ import { discoveryMiddleware } from "../src/discovery.js";
9
+ const app = express();
10
+ const PORT = process.env.PORT || 3000;
11
+ /**
12
+ * LAFS-compliant envelope endpoint handler
13
+ * Demonstrates proper LAFS envelope processing
14
+ */
15
+ app.post("/api/v1/envelope", express.json(), (req, res) => {
16
+ const envelope = req.body;
17
+ // Validate basic envelope structure
18
+ if (!envelope._meta) {
19
+ return res.status(400).json({
20
+ success: false,
21
+ error: {
22
+ code: "INVALID_ENVELOPE",
23
+ message: "Missing _meta field",
24
+ category: "VALIDATION",
25
+ retryable: false,
26
+ retryAfterMs: null,
27
+ details: { missing: ["_meta"] }
28
+ },
29
+ result: null
30
+ });
31
+ }
32
+ // Process the request (simplified example)
33
+ const operation = envelope._meta.operation;
34
+ // Echo back with success
35
+ res.json({
36
+ $schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
37
+ _meta: {
38
+ specVersion: envelope._meta.specVersion || "1.0.0",
39
+ schemaVersion: envelope._meta.schemaVersion || "1.0.0",
40
+ timestamp: new Date().toISOString(),
41
+ operation: `${operation}:response`,
42
+ requestId: envelope._meta.requestId || crypto.randomUUID(),
43
+ transport: "http",
44
+ strict: envelope._meta.strict ?? true,
45
+ mvi: envelope._meta.mvi || "standard",
46
+ contextVersion: (envelope._meta.contextVersion || 0) + 1
47
+ },
48
+ success: true,
49
+ result: {
50
+ received: true,
51
+ operation: operation,
52
+ data: envelope.payload || null
53
+ },
54
+ error: null
55
+ });
56
+ });
57
+ /**
58
+ * Context ledger endpoint
59
+ * Demonstrates context management capability
60
+ */
61
+ app.get("/api/v1/context/:ledgerId", (req, res) => {
62
+ const ledgerId = req.params.ledgerId;
63
+ // Return mock context ledger
64
+ res.json({
65
+ $schema: "https://lafs.dev/schemas/v1/context-ledger.schema.json",
66
+ ledgerId,
67
+ version: 1,
68
+ createdAt: new Date().toISOString(),
69
+ updatedAt: new Date().toISOString(),
70
+ entries: [],
71
+ checksum: "sha256:mock",
72
+ maxEntries: 1000
73
+ });
74
+ });
75
+ app.post("/api/v1/context/:ledgerId/entries", express.json(), (req, res) => {
76
+ const ledgerId = req.params.ledgerId;
77
+ const entry = req.body;
78
+ res.json({
79
+ $schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
80
+ _meta: {
81
+ specVersion: "1.0.0",
82
+ schemaVersion: "1.0.0",
83
+ timestamp: new Date().toISOString(),
84
+ operation: "context:append",
85
+ requestId: crypto.randomUUID(),
86
+ transport: "http",
87
+ strict: true,
88
+ mvi: "standard",
89
+ contextVersion: 2
90
+ },
91
+ success: true,
92
+ result: {
93
+ ledgerId,
94
+ entryId: crypto.randomUUID(),
95
+ committed: true,
96
+ timestamp: new Date().toISOString()
97
+ },
98
+ error: null
99
+ });
100
+ });
101
+ /**
102
+ * Discovery configuration
103
+ * Advertises all LAFS capabilities and endpoints
104
+ */
105
+ const discoveryConfig = {
106
+ service: {
107
+ name: "example-lafs-service",
108
+ version: "1.0.0",
109
+ description: "Example LAFS-compliant API service demonstrating discovery protocol"
110
+ },
111
+ capabilities: [
112
+ {
113
+ name: "envelope-processor",
114
+ version: "1.0.0",
115
+ description: "Process and validate LAFS envelopes",
116
+ operations: ["process", "validate", "transform"]
117
+ },
118
+ {
119
+ name: "context-ledger",
120
+ version: "1.0.0",
121
+ description: "Manage context ledgers for stateful operations",
122
+ operations: ["read", "append", "query"]
123
+ },
124
+ {
125
+ name: "pagination-provider",
126
+ version: "1.0.0",
127
+ description: "Provide cursor and offset pagination for list endpoints",
128
+ operations: ["cursor", "offset", "none"],
129
+ optional: true
130
+ }
131
+ ],
132
+ endpoints: {
133
+ envelope: "/api/v1/envelope",
134
+ context: "/api/v1/context",
135
+ discovery: "https://lafs.dev/schemas/v1/discovery.schema.json"
136
+ },
137
+ cacheMaxAge: 3600,
138
+ lafsVersion: "1.0.0"
139
+ };
140
+ // Mount discovery middleware BEFORE other routes
141
+ // This ensures /.well-known/lafs.json is served at the root
142
+ app.use(discoveryMiddleware(discoveryConfig));
143
+ /**
144
+ * Health check endpoint
145
+ */
146
+ app.get("/health", (req, res) => {
147
+ res.json({
148
+ status: "healthy",
149
+ service: discoveryConfig.service.name,
150
+ version: discoveryConfig.service.version,
151
+ timestamp: new Date().toISOString()
152
+ });
153
+ });
154
+ /**
155
+ * Error handling middleware
156
+ */
157
+ app.use((err, req, res, next) => {
158
+ console.error("Error:", err);
159
+ res.status(500).json({
160
+ success: false,
161
+ error: {
162
+ code: "INTERNAL_ERROR",
163
+ message: err.message || "Internal server error",
164
+ category: "INTERNAL",
165
+ retryable: false,
166
+ retryAfterMs: null,
167
+ details: process.env.NODE_ENV === "development" ? { stack: err.stack } : {}
168
+ },
169
+ result: null
170
+ });
171
+ });
172
+ /**
173
+ * Start server
174
+ */
175
+ const server = app.listen(PORT, () => {
176
+ console.log(`
177
+ ╔════════════════════════════════════════════════════════╗
178
+ ║ LAFS Discovery Server Running ║
179
+ ╠════════════════════════════════════════════════════════╣
180
+ ║ Service: ${discoveryConfig.service.name.padEnd(43)}║
181
+ ║ Version: ${discoveryConfig.service.version.padEnd(43)}║
182
+ ║ Port: ${String(PORT).padEnd(43)}║
183
+ ╠════════════════════════════════════════════════════════╣
184
+ ║ Endpoints: ║
185
+ ║ GET /.well-known/lafs.json (Discovery document) ║
186
+ ║ POST /api/v1/envelope (Envelope processor) ║
187
+ ║ GET /api/v1/context/:id (Context ledger) ║
188
+ ║ GET /health (Health check) ║
189
+ ╚════════════════════════════════════════════════════════╝
190
+
191
+ Test with:
192
+ curl http://localhost:${PORT}/.well-known/lafs.json | jq
193
+ curl http://localhost:${PORT}/health | jq
194
+ curl -X POST http://localhost:${PORT}/api/v1/envelope \
195
+ -H "Content-Type: application/json" \
196
+ -d '{"_meta":{"operation":"test","requestId":"123"},"payload":{"hello":"world"}}'
197
+ `);
198
+ });
199
+ /**
200
+ * Graceful shutdown
201
+ */
202
+ process.on("SIGTERM", () => {
203
+ console.log("\nShutting down gracefully...");
204
+ server.close(() => {
205
+ console.log("Server closed");
206
+ process.exit(0);
207
+ });
208
+ });
209
+ process.on("SIGINT", () => {
210
+ console.log("\nShutting down gracefully...");
211
+ server.close(() => {
212
+ console.log("Server closed");
213
+ process.exit(0);
214
+ });
215
+ });
216
+ export default app;
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP-LAFS Client Example
4
+ *
5
+ * A client that connects to the MCP-LAFS server and validates responses
6
+ * are LAFS-compliant. Demonstrates budget negotiation and envelope validation.
7
+ *
8
+ * Usage: npx ts-node examples/mcp-lafs-client.ts
9
+ */
10
+ export {};
@@ -0,0 +1,427 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP-LAFS Client Example
4
+ *
5
+ * A client that connects to the MCP-LAFS server and validates responses
6
+ * are LAFS-compliant. Demonstrates budget negotiation and envelope validation.
7
+ *
8
+ * Usage: npx ts-node examples/mcp-lafs-client.ts
9
+ */
10
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
11
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
12
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
13
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
14
+ import { wrapMCPResult } from "../src/mcpAdapter.js";
15
+ import { validateEnvelope } from "../src/validateEnvelope.js";
16
+ // Colors for console output
17
+ const colors = {
18
+ reset: "\x1b[0m",
19
+ green: "\x1b[32m",
20
+ red: "\x1b[31m",
21
+ yellow: "\x1b[33m",
22
+ blue: "\x1b[34m",
23
+ cyan: "\x1b[36m",
24
+ };
25
+ function logSuccess(message) {
26
+ console.log(`${colors.green}✓${colors.reset} ${message}`);
27
+ }
28
+ function logError(message) {
29
+ console.log(`${colors.red}✗${colors.reset} ${message}`);
30
+ }
31
+ function logInfo(message) {
32
+ console.log(`${colors.blue}ℹ${colors.reset} ${message}`);
33
+ }
34
+ function logHeader(message) {
35
+ console.log(`\n${colors.cyan}${message}${colors.reset}`);
36
+ console.log("=".repeat(message.length));
37
+ }
38
+ // Embedded server setup for in-memory testing
39
+ // In production, this would connect to an external server process
40
+ async function createEmbeddedServer() {
41
+ const server = new Server({
42
+ name: "lafs-mcp-server-embedded",
43
+ version: "1.0.0",
44
+ }, {
45
+ capabilities: {
46
+ tools: {},
47
+ },
48
+ });
49
+ // Simulated database
50
+ const simulatedDatabase = new Map([
51
+ ["1", { id: "1", name: "Product A", value: 100 }],
52
+ ["2", { id: "2", name: "Product B", value: 200 }],
53
+ ["3", { id: "3", name: "Product C", value: 300 }],
54
+ ]);
55
+ // Tool handlers
56
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
57
+ tools: [
58
+ {
59
+ name: "weather",
60
+ description: "Get current weather for a location",
61
+ inputSchema: {
62
+ type: "object",
63
+ properties: {
64
+ location: { type: "string" },
65
+ units: { type: "string", enum: ["celsius", "fahrenheit"] },
66
+ _budget: { type: "number", minimum: 10, maximum: 10000 },
67
+ },
68
+ required: ["location"],
69
+ },
70
+ },
71
+ {
72
+ name: "calculator",
73
+ description: "Perform mathematical calculations",
74
+ inputSchema: {
75
+ type: "object",
76
+ properties: {
77
+ operation: { type: "string", enum: ["add", "subtract", "multiply", "divide"] },
78
+ a: { type: "number" },
79
+ b: { type: "number" },
80
+ _budget: { type: "number", minimum: 10, maximum: 1000 },
81
+ },
82
+ required: ["operation", "a", "b"],
83
+ },
84
+ },
85
+ {
86
+ name: "database_query",
87
+ description: "Query the simulated database",
88
+ inputSchema: {
89
+ type: "object",
90
+ properties: {
91
+ action: { type: "string", enum: ["get", "list", "search"] },
92
+ id: { type: "string" },
93
+ query: { type: "string" },
94
+ _budget: { type: "number", minimum: 10, maximum: 5000 },
95
+ },
96
+ required: ["action"],
97
+ },
98
+ },
99
+ ],
100
+ }));
101
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
102
+ const { name, arguments: args } = request.params;
103
+ const budget = typeof args?._budget === "number" ? args._budget : undefined;
104
+ try {
105
+ let result;
106
+ switch (name) {
107
+ case "weather": {
108
+ const location = String(args?.location ?? "Unknown");
109
+ const units = String(args?.units ?? "celsius");
110
+ result = {
111
+ location,
112
+ temperature: units === "fahrenheit" ? 72 : 22,
113
+ temperatureUnit: units,
114
+ conditions: "sunny",
115
+ humidity: 45,
116
+ windSpeed: 10,
117
+ forecast: [
118
+ { day: "Today", high: 24, low: 18, condition: "sunny" },
119
+ { day: "Tomorrow", high: 25, low: 19, condition: "partly cloudy" },
120
+ { day: "Day after", high: 23, low: 17, condition: "clear" },
121
+ ],
122
+ };
123
+ break;
124
+ }
125
+ case "calculator": {
126
+ const operation = String(args?.operation);
127
+ const a = Number(args?.a);
128
+ const b = Number(args?.b);
129
+ let calcResult;
130
+ switch (operation) {
131
+ case "add":
132
+ calcResult = a + b;
133
+ break;
134
+ case "subtract":
135
+ calcResult = a - b;
136
+ break;
137
+ case "multiply":
138
+ calcResult = a * b;
139
+ break;
140
+ case "divide":
141
+ if (b === 0)
142
+ throw new Error("Cannot divide by zero");
143
+ calcResult = a / b;
144
+ break;
145
+ default: throw new Error(`Unknown operation: ${operation}`);
146
+ }
147
+ result = {
148
+ operation,
149
+ expression: `${a} ${operation} ${b}`,
150
+ operands: { a, b },
151
+ result: calcResult,
152
+ };
153
+ break;
154
+ }
155
+ case "database_query": {
156
+ const action = String(args?.action);
157
+ switch (action) {
158
+ case "get": {
159
+ const id = String(args?.id);
160
+ const record = simulatedDatabase.get(id);
161
+ if (!record)
162
+ throw new Error(`Record ${id} not found`);
163
+ result = { action, record, found: true };
164
+ break;
165
+ }
166
+ case "list": {
167
+ const records = Array.from(simulatedDatabase.values());
168
+ result = { action, records, count: records.length, total: records.length };
169
+ break;
170
+ }
171
+ case "search": {
172
+ const query = String(args?.query ?? "").toLowerCase();
173
+ const records = Array.from(simulatedDatabase.values()).filter((r) => r.name.toLowerCase().includes(query));
174
+ result = { action, query, records, count: records.length, total: simulatedDatabase.size };
175
+ break;
176
+ }
177
+ default:
178
+ throw new Error(`Unknown action: ${action}`);
179
+ }
180
+ break;
181
+ }
182
+ default:
183
+ throw new Error(`Unknown tool: ${name}`);
184
+ }
185
+ const mcpResult = {
186
+ content: [{ type: "text", text: JSON.stringify(result) }],
187
+ isError: false,
188
+ };
189
+ const envelope = wrapMCPResult(mcpResult, `tools/${name}`, budget);
190
+ return {
191
+ content: [{ type: "text", text: JSON.stringify(envelope) }],
192
+ };
193
+ }
194
+ catch (error) {
195
+ const errorMessage = error instanceof Error ? error.message : String(error);
196
+ const mcpResult = {
197
+ content: [{ type: "text", text: errorMessage }],
198
+ isError: true,
199
+ };
200
+ const envelope = wrapMCPResult(mcpResult, `tools/${name}`, budget);
201
+ return {
202
+ content: [{ type: "text", text: JSON.stringify(envelope) }],
203
+ isError: true,
204
+ };
205
+ }
206
+ });
207
+ return server;
208
+ }
209
+ // Client class that connects and validates LAFS responses
210
+ class LAFSMCPClient {
211
+ client;
212
+ validations = [];
213
+ constructor() {
214
+ this.client = new Client({
215
+ name: "lafs-mcp-client",
216
+ version: "1.0.0",
217
+ }, {
218
+ capabilities: {},
219
+ });
220
+ }
221
+ async connect(server) {
222
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
223
+ await Promise.all([
224
+ this.client.connect(clientTransport),
225
+ server.connect(serverTransport),
226
+ ]);
227
+ }
228
+ async listTools() {
229
+ const response = await this.client.listTools();
230
+ return response.tools.map((t) => t.name);
231
+ }
232
+ async callTool(name, args) {
233
+ const result = await this.client.callTool({
234
+ name,
235
+ arguments: args,
236
+ });
237
+ // Extract LAFS envelope from MCP response
238
+ const content = result.content;
239
+ const textContent = content.find((c) => c.type === "text");
240
+ if (!textContent || !("text" in textContent) || !textContent.text) {
241
+ throw new Error("No text content in MCP response");
242
+ }
243
+ const envelope = JSON.parse(textContent.text);
244
+ // Validate against LAFS schema
245
+ const validation = validateEnvelope(envelope);
246
+ this.validations.push({
247
+ tool: name,
248
+ valid: validation.valid,
249
+ errors: validation.errors,
250
+ });
251
+ return { envelope, validation };
252
+ }
253
+ getValidationSummary() {
254
+ return {
255
+ total: this.validations.length,
256
+ passed: this.validations.filter((v) => v.valid).length,
257
+ failed: this.validations.filter((v) => !v.valid).length,
258
+ };
259
+ }
260
+ printValidationReport() {
261
+ console.log("\n" + "=".repeat(60));
262
+ console.log("LAFS VALIDATION REPORT");
263
+ console.log("=".repeat(60));
264
+ for (const validation of this.validations) {
265
+ if (validation.valid) {
266
+ logSuccess(`${validation.tool}: Valid LAFS envelope`);
267
+ }
268
+ else {
269
+ logError(`${validation.tool}: Invalid LAFS envelope`);
270
+ for (const error of validation.errors) {
271
+ console.log(` - ${error}`);
272
+ }
273
+ }
274
+ }
275
+ const summary = this.getValidationSummary();
276
+ console.log("\n" + "-".repeat(60));
277
+ console.log(`Total: ${summary.total} | Passed: ${summary.passed} | Failed: ${summary.failed}`);
278
+ console.log("=".repeat(60));
279
+ }
280
+ }
281
+ // Main demonstration
282
+ async function main() {
283
+ logHeader("LAFS-MCP Integration Demo");
284
+ logInfo("Starting embedded MCP server with LAFS envelope wrapping...");
285
+ // Create and start embedded server
286
+ const server = await createEmbeddedServer();
287
+ const client = new LAFSMCPClient();
288
+ await client.connect(server);
289
+ logSuccess("Connected to MCP server\n");
290
+ // List available tools
291
+ logHeader("Available Tools");
292
+ const tools = await client.listTools();
293
+ for (const tool of tools) {
294
+ console.log(` • ${tool}`);
295
+ }
296
+ // Test 1: Weather tool
297
+ logHeader("Test 1: Weather Tool (Standard)");
298
+ try {
299
+ const { envelope, validation } = await client.callTool("weather", {
300
+ location: "San Francisco",
301
+ units: "celsius",
302
+ });
303
+ if (validation.valid) {
304
+ logSuccess("Response is valid LAFS envelope");
305
+ console.log("\nResponse Metadata:");
306
+ console.log(` Spec Version: ${envelope._meta.specVersion}`);
307
+ console.log(` Operation: ${envelope._meta.operation}`);
308
+ console.log(` Success: ${envelope.success}`);
309
+ console.log(` Timestamp: ${envelope._meta.timestamp}`);
310
+ console.log("\nResult:");
311
+ console.log(JSON.stringify(envelope.result, null, 2));
312
+ }
313
+ else {
314
+ logError("Response failed LAFS validation");
315
+ validation.errors.forEach((e) => console.log(` - ${e}`));
316
+ }
317
+ }
318
+ catch (error) {
319
+ logError(`Test failed: ${error instanceof Error ? error.message : error}`);
320
+ }
321
+ // Test 2: Calculator with budget
322
+ logHeader("Test 2: Calculator Tool (With Budget)");
323
+ try {
324
+ const { envelope, validation } = await client.callTool("calculator", {
325
+ operation: "multiply",
326
+ a: 42,
327
+ b: 100,
328
+ _budget: 50,
329
+ });
330
+ if (validation.valid) {
331
+ logSuccess("Response is valid LAFS envelope");
332
+ const metaWithBudget = envelope._meta;
333
+ if (metaWithBudget._tokenEstimate) {
334
+ console.log("\nBudget Information:");
335
+ console.log(` Estimated Tokens: ${metaWithBudget._tokenEstimate.estimated}`);
336
+ console.log(` Truncated: ${metaWithBudget._tokenEstimate.truncated ?? false}`);
337
+ if (metaWithBudget._tokenEstimate.originalEstimate) {
338
+ console.log(` Original Estimate: ${metaWithBudget._tokenEstimate.originalEstimate}`);
339
+ }
340
+ }
341
+ console.log("\nResult:");
342
+ console.log(JSON.stringify(envelope.result, null, 2));
343
+ }
344
+ else {
345
+ logError("Response failed LAFS validation");
346
+ validation.errors.forEach((e) => console.log(` - ${e}`));
347
+ }
348
+ }
349
+ catch (error) {
350
+ logError(`Test failed: ${error instanceof Error ? error.message : error}`);
351
+ }
352
+ // Test 3: Database query
353
+ logHeader("Test 3: Database Query Tool");
354
+ try {
355
+ const { envelope, validation } = await client.callTool("database_query", {
356
+ action: "list",
357
+ });
358
+ if (validation.valid) {
359
+ logSuccess("Response is valid LAFS envelope");
360
+ console.log("\nResult:");
361
+ console.log(JSON.stringify(envelope.result, null, 2));
362
+ }
363
+ else {
364
+ logError("Response failed LAFS validation");
365
+ validation.errors.forEach((e) => console.log(` - ${e}`));
366
+ }
367
+ }
368
+ catch (error) {
369
+ logError(`Test failed: ${error instanceof Error ? error.message : error}`);
370
+ }
371
+ // Test 4: Error handling
372
+ logHeader("Test 4: Error Handling (Division by Zero)");
373
+ try {
374
+ const { envelope, validation } = await client.callTool("calculator", {
375
+ operation: "divide",
376
+ a: 10,
377
+ b: 0,
378
+ });
379
+ if (validation.valid) {
380
+ logSuccess("Error response is valid LAFS envelope");
381
+ console.log("\nError Details:");
382
+ console.log(` Success: ${envelope.success}`);
383
+ console.log(` Error Code: ${envelope.error?.code}`);
384
+ console.log(` Category: ${envelope.error?.category}`);
385
+ console.log(` Retryable: ${envelope.error?.retryable}`);
386
+ console.log(` Message: ${envelope.error?.message}`);
387
+ }
388
+ else {
389
+ logError("Error response failed LAFS validation");
390
+ validation.errors.forEach((e) => console.log(` - ${e}`));
391
+ }
392
+ }
393
+ catch (error) {
394
+ logError(`Test failed: ${error instanceof Error ? error.message : error}`);
395
+ }
396
+ // Test 5: Database not found
397
+ logHeader("Test 5: Not Found Error");
398
+ try {
399
+ const { envelope, validation } = await client.callTool("database_query", {
400
+ action: "get",
401
+ id: "999",
402
+ });
403
+ if (validation.valid) {
404
+ logSuccess("Not found error is valid LAFS envelope");
405
+ console.log("\nError Details:");
406
+ console.log(` Success: ${envelope.success}`);
407
+ console.log(` Error Code: ${envelope.error?.code}`);
408
+ console.log(` Category: ${envelope.error?.category}`);
409
+ }
410
+ else {
411
+ logError("Error response failed LAFS validation");
412
+ validation.errors.forEach((e) => console.log(` - ${e}`));
413
+ }
414
+ }
415
+ catch (error) {
416
+ logError(`Test failed: ${error instanceof Error ? error.message : error}`);
417
+ }
418
+ // Print final validation report
419
+ client.printValidationReport();
420
+ logHeader("Demo Complete");
421
+ logInfo("All MCP tool responses are wrapped in LAFS-compliant envelopes");
422
+ logInfo("This proves LAFS complements MCP by adding structured metadata");
423
+ }
424
+ main().catch((error) => {
425
+ console.error("Fatal error:", error);
426
+ process.exit(1);
427
+ });
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP-LAFS Server Example
4
+ *
5
+ * A working MCP server that wraps all tool responses in LAFS-compliant envelopes.
6
+ * Demonstrates how LAFS complements MCP by adding structured metadata and budget enforcement.
7
+ *
8
+ * Usage: npx ts-node examples/mcp-lafs-server.ts
9
+ */
10
+ export {};