@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
@@ -0,0 +1,358 @@
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
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
11
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
13
+ import { wrapMCPResult } from "../src/mcpAdapter.js";
14
+ const simulatedDatabase = new Map([
15
+ ["1", { id: "1", name: "Product A", value: 100, createdAt: new Date().toISOString() }],
16
+ ["2", { id: "2", name: "Product B", value: 200, createdAt: new Date().toISOString() }],
17
+ ["3", { id: "3", name: "Product C", value: 300, createdAt: new Date().toISOString() }],
18
+ ]);
19
+ // Tool definitions
20
+ const TOOLS = [
21
+ {
22
+ name: "weather",
23
+ description: "Get current weather for a location",
24
+ inputSchema: {
25
+ type: "object",
26
+ properties: {
27
+ location: {
28
+ type: "string",
29
+ description: "City name or coordinates",
30
+ },
31
+ units: {
32
+ type: "string",
33
+ enum: ["celsius", "fahrenheit"],
34
+ description: "Temperature units",
35
+ default: "celsius",
36
+ },
37
+ _budget: {
38
+ type: "number",
39
+ description: "Token budget for response (LAFS extension)",
40
+ minimum: 10,
41
+ maximum: 10000,
42
+ },
43
+ },
44
+ required: ["location"],
45
+ },
46
+ },
47
+ {
48
+ name: "calculator",
49
+ description: "Perform mathematical calculations",
50
+ inputSchema: {
51
+ type: "object",
52
+ properties: {
53
+ operation: {
54
+ type: "string",
55
+ enum: ["add", "subtract", "multiply", "divide", "power", "sqrt"],
56
+ description: "Mathematical operation to perform",
57
+ },
58
+ a: {
59
+ type: "number",
60
+ description: "First operand",
61
+ },
62
+ b: {
63
+ type: "number",
64
+ description: "Second operand (not needed for sqrt)",
65
+ },
66
+ _budget: {
67
+ type: "number",
68
+ description: "Token budget for response (LAFS extension)",
69
+ minimum: 10,
70
+ maximum: 1000,
71
+ },
72
+ },
73
+ required: ["operation", "a"],
74
+ },
75
+ },
76
+ {
77
+ name: "database_query",
78
+ description: "Query the simulated database",
79
+ inputSchema: {
80
+ type: "object",
81
+ properties: {
82
+ action: {
83
+ type: "string",
84
+ enum: ["get", "list", "search"],
85
+ description: "Query action to perform",
86
+ },
87
+ id: {
88
+ type: "string",
89
+ description: "Record ID (for get action)",
90
+ },
91
+ query: {
92
+ type: "string",
93
+ description: "Search query (for search action)",
94
+ },
95
+ limit: {
96
+ type: "number",
97
+ description: "Maximum results to return",
98
+ default: 10,
99
+ },
100
+ _budget: {
101
+ type: "number",
102
+ description: "Token budget for response (LAFS extension)",
103
+ minimum: 10,
104
+ maximum: 5000,
105
+ },
106
+ },
107
+ required: ["action"],
108
+ },
109
+ },
110
+ ];
111
+ // Weather simulation
112
+ async function getWeather(location, units) {
113
+ // Simulate API call delay
114
+ await new Promise((resolve) => setTimeout(resolve, 100));
115
+ // Generate deterministic but varied weather based on location
116
+ const hash = location.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
117
+ const conditions = ["sunny", "cloudy", "rainy", "partly cloudy", "clear"];
118
+ const condition = conditions[hash % conditions.length];
119
+ // Temperature based on condition and some randomness
120
+ let baseTemp = 20; // celsius
121
+ if (condition === "sunny")
122
+ baseTemp = 25;
123
+ if (condition === "rainy")
124
+ baseTemp = 15;
125
+ if (condition === "clear")
126
+ baseTemp = 22;
127
+ const tempC = baseTemp + (hash % 10) - 5;
128
+ const tempF = Math.round((tempC * 9) / 5 + 32);
129
+ return {
130
+ location,
131
+ temperature: units === "fahrenheit" ? tempF : tempC,
132
+ temperatureUnit: units,
133
+ conditions: condition,
134
+ humidity: 40 + (hash % 50),
135
+ windSpeed: 5 + (hash % 20),
136
+ windUnit: "km/h",
137
+ forecast: [
138
+ { day: "Today", high: tempC + 2, low: tempC - 3, condition },
139
+ { day: "Tomorrow", high: tempC + 1, low: tempC - 4, condition: conditions[(hash + 1) % conditions.length] },
140
+ { day: "Day after", high: tempC + 3, low: tempC - 2, condition: conditions[(hash + 2) % conditions.length] },
141
+ ],
142
+ };
143
+ }
144
+ // Calculator implementation
145
+ function calculate(operation, a, b) {
146
+ let result;
147
+ let expression;
148
+ switch (operation) {
149
+ case "add":
150
+ if (b === undefined)
151
+ throw new Error("Second operand (b) required for addition");
152
+ result = a + b;
153
+ expression = `${a} + ${b}`;
154
+ break;
155
+ case "subtract":
156
+ if (b === undefined)
157
+ throw new Error("Second operand (b) required for subtraction");
158
+ result = a - b;
159
+ expression = `${a} - ${b}`;
160
+ break;
161
+ case "multiply":
162
+ if (b === undefined)
163
+ throw new Error("Second operand (b) required for multiplication");
164
+ result = a * b;
165
+ expression = `${a} * ${b}`;
166
+ break;
167
+ case "divide":
168
+ if (b === undefined)
169
+ throw new Error("Second operand (b) required for division");
170
+ if (b === 0)
171
+ throw new Error("Cannot divide by zero");
172
+ result = a / b;
173
+ expression = `${a} / ${b}`;
174
+ break;
175
+ case "power":
176
+ if (b === undefined)
177
+ throw new Error("Second operand (b) required for power operation");
178
+ result = Math.pow(a, b);
179
+ expression = `${a} ^ ${b}`;
180
+ break;
181
+ case "sqrt":
182
+ if (a < 0)
183
+ throw new Error("Cannot calculate square root of negative number");
184
+ result = Math.sqrt(a);
185
+ expression = `sqrt(${a})`;
186
+ break;
187
+ default:
188
+ throw new Error(`Unknown operation: ${operation}`);
189
+ }
190
+ return {
191
+ operation,
192
+ expression,
193
+ operands: { a, b },
194
+ result,
195
+ resultType: Number.isInteger(result) ? "integer" : "float",
196
+ };
197
+ }
198
+ // Database operations
199
+ function databaseQuery(action, id, query, limit) {
200
+ switch (action) {
201
+ case "get": {
202
+ if (!id) {
203
+ throw new Error("ID required for get action");
204
+ }
205
+ const record = simulatedDatabase.get(id);
206
+ if (!record) {
207
+ throw new Error(`Record with ID '${id}' not found`);
208
+ }
209
+ return {
210
+ action,
211
+ record,
212
+ found: true,
213
+ };
214
+ }
215
+ case "list": {
216
+ const records = Array.from(simulatedDatabase.values()).slice(0, limit ?? 10);
217
+ return {
218
+ action,
219
+ records,
220
+ count: records.length,
221
+ total: simulatedDatabase.size,
222
+ };
223
+ }
224
+ case "search": {
225
+ if (!query) {
226
+ throw new Error("Query required for search action");
227
+ }
228
+ const queryLower = query.toLowerCase();
229
+ const records = Array.from(simulatedDatabase.values())
230
+ .filter((r) => r.name.toLowerCase().includes(queryLower))
231
+ .slice(0, limit ?? 10);
232
+ return {
233
+ action,
234
+ query,
235
+ records,
236
+ count: records.length,
237
+ total: simulatedDatabase.size,
238
+ };
239
+ }
240
+ default:
241
+ throw new Error(`Unknown action: ${action}`);
242
+ }
243
+ }
244
+ // Create MCP server
245
+ const server = new Server({
246
+ name: "lafs-mcp-server",
247
+ version: "1.0.0",
248
+ }, {
249
+ capabilities: {
250
+ tools: {},
251
+ },
252
+ });
253
+ // Handle tool listing
254
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
255
+ return {
256
+ tools: TOOLS,
257
+ };
258
+ });
259
+ // Handle tool calls
260
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
261
+ const { name, arguments: args } = request.params;
262
+ const budget = typeof args?._budget === "number" ? args._budget : undefined;
263
+ try {
264
+ let result;
265
+ switch (name) {
266
+ case "weather": {
267
+ const location = String(args?.location ?? "");
268
+ const units = String(args?.units ?? "celsius");
269
+ if (!location) {
270
+ throw new Error("Location is required");
271
+ }
272
+ result = await getWeather(location, units);
273
+ break;
274
+ }
275
+ case "calculator": {
276
+ const operation = String(args?.operation ?? "");
277
+ const a = Number(args?.a);
278
+ const b = args?.b !== undefined ? Number(args?.b) : undefined;
279
+ if (!operation || Number.isNaN(a)) {
280
+ throw new Error("Operation and operand 'a' are required");
281
+ }
282
+ result = calculate(operation, a, b);
283
+ break;
284
+ }
285
+ case "database_query": {
286
+ const action = String(args?.action ?? "");
287
+ const id = args?.id !== undefined ? String(args?.id) : undefined;
288
+ const query = args?.query !== undefined ? String(args?.query) : undefined;
289
+ const limit = args?.limit !== undefined ? Number(args?.limit) : undefined;
290
+ if (!action) {
291
+ throw new Error("Action is required");
292
+ }
293
+ result = databaseQuery(action, id, query, limit);
294
+ break;
295
+ }
296
+ default:
297
+ throw new Error(`Unknown tool: ${name}`);
298
+ }
299
+ // Create MCP result
300
+ const mcpResult = {
301
+ content: [
302
+ {
303
+ type: "text",
304
+ text: JSON.stringify(result, null, 2),
305
+ },
306
+ ],
307
+ isError: false,
308
+ };
309
+ // Wrap in LAFS envelope
310
+ const envelope = wrapMCPResult(mcpResult, `tools/${name}`, budget);
311
+ // Return the LAFS envelope as text content
312
+ return {
313
+ content: [
314
+ {
315
+ type: "text",
316
+ text: JSON.stringify(envelope),
317
+ },
318
+ ],
319
+ };
320
+ }
321
+ catch (error) {
322
+ // Create error MCP result
323
+ const errorMessage = error instanceof Error ? error.message : String(error);
324
+ const mcpResult = {
325
+ content: [
326
+ {
327
+ type: "text",
328
+ text: errorMessage,
329
+ },
330
+ ],
331
+ isError: true,
332
+ };
333
+ // Wrap in LAFS error envelope
334
+ const envelope = wrapMCPResult(mcpResult, `tools/${name}`, budget);
335
+ return {
336
+ content: [
337
+ {
338
+ type: "text",
339
+ text: JSON.stringify(envelope),
340
+ },
341
+ ],
342
+ isError: true,
343
+ };
344
+ }
345
+ });
346
+ // Start server
347
+ async function main() {
348
+ const transport = new StdioServerTransport();
349
+ console.error("LAFS-MCP Server starting...");
350
+ console.error("Available tools: weather, calculator, database_query");
351
+ console.error("All responses are wrapped in LAFS-compliant envelopes");
352
+ await server.connect(transport);
353
+ console.error("LAFS-MCP Server running on stdio");
354
+ }
355
+ main().catch((error) => {
356
+ console.error("Fatal error:", error);
357
+ process.exit(1);
358
+ });
File without changes
File without changes
@@ -0,0 +1,84 @@
1
+ /**
2
+ * LAFS Budget Enforcement
3
+ *
4
+ * Middleware for enforcing MVI (Minimal Viable Interface) token budgets on LAFS envelopes.
5
+ * Provides budget checking, truncation, and error generation for exceeded budgets.
6
+ */
7
+ import type { LAFSEnvelope } from "./types.js";
8
+ import type { BudgetEnforcementOptions, TokenEstimate, BudgetEnforcementResult } from "./types.js";
9
+ import { TokenEstimator } from "./tokenEstimator.js";
10
+ /**
11
+ * Budget exceeded error code from LAFS error registry
12
+ */
13
+ declare const BUDGET_EXCEEDED_CODE = "E_MVI_BUDGET_EXCEEDED";
14
+ /**
15
+ * Apply budget enforcement to an envelope.
16
+ *
17
+ * @param envelope - The LAFS envelope to check
18
+ * @param budget - Maximum allowed tokens
19
+ * @param options - Budget enforcement options
20
+ * @returns Enforce result with potentially modified envelope
21
+ */
22
+ export declare function applyBudgetEnforcement(envelope: LAFSEnvelope, budget: number, options?: BudgetEnforcementOptions): BudgetEnforcementResult;
23
+ /**
24
+ * Type for middleware function
25
+ */
26
+ type EnvelopeMiddleware = (envelope: LAFSEnvelope, next: () => LAFSEnvelope | Promise<LAFSEnvelope>) => Promise<LAFSEnvelope> | LAFSEnvelope;
27
+ /**
28
+ * Create a budget enforcement middleware function.
29
+ *
30
+ * @param budget - Maximum allowed tokens for response
31
+ * @param options - Budget enforcement options
32
+ * @returns Middleware function that enforces budget
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const middleware = withBudget(1000, { truncateOnExceed: true });
37
+ * const result = await middleware(envelope, async () => nextEnvelope);
38
+ * ```
39
+ */
40
+ export declare function withBudget(budget: number, options?: BudgetEnforcementOptions): EnvelopeMiddleware;
41
+ /**
42
+ * Check if an envelope has exceeded its budget without modifying it.
43
+ *
44
+ * @param envelope - The LAFS envelope to check
45
+ * @param budget - Maximum allowed tokens
46
+ * @returns Budget check result
47
+ */
48
+ export declare function checkBudget(envelope: LAFSEnvelope, budget: number): {
49
+ exceeded: boolean;
50
+ estimated: number;
51
+ remaining: number;
52
+ };
53
+ /**
54
+ * Synchronous version of withBudget for non-async contexts.
55
+ *
56
+ * @param budget - Maximum allowed tokens for response
57
+ * @param options - Budget enforcement options
58
+ * @returns Middleware function that enforces budget synchronously
59
+ */
60
+ export declare function withBudgetSync(budget: number, options?: BudgetEnforcementOptions): (envelope: LAFSEnvelope, next: () => LAFSEnvelope) => LAFSEnvelope;
61
+ /**
62
+ * Higher-order function that wraps a handler with budget enforcement.
63
+ *
64
+ * @param handler - The handler function to wrap
65
+ * @param budget - Maximum allowed tokens
66
+ * @param options - Budget enforcement options
67
+ * @returns Wrapped handler with budget enforcement
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * const myHandler = async (request: Request) => ({ success: true, result: { data } });
72
+ * const budgetedHandler = wrapWithBudget(myHandler, 1000, { truncateOnExceed: true });
73
+ * const result = await budgetedHandler(request);
74
+ * ```
75
+ */
76
+ export declare function wrapWithBudget<TArgs extends unknown[], TResult extends LAFSEnvelope>(handler: (...args: TArgs) => TResult | Promise<TResult>, budget: number, options?: BudgetEnforcementOptions): (...args: TArgs) => Promise<LAFSEnvelope>;
77
+ /**
78
+ * Compose multiple middleware functions into a single middleware.
79
+ * Middleware is executed in order (left to right).
80
+ */
81
+ export declare function composeMiddleware(...middlewares: EnvelopeMiddleware[]): EnvelopeMiddleware;
82
+ export type { BudgetEnforcementOptions, TokenEstimate, BudgetEnforcementResult };
83
+ export { TokenEstimator };
84
+ export { BUDGET_EXCEEDED_CODE };