@cleocode/lafs-protocol 0.1.1 → 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.
- package/README.md +129 -23
- package/dist/examples/discovery-server.d.ts +8 -0
- package/dist/examples/discovery-server.js +216 -0
- package/dist/examples/mcp-lafs-client.d.ts +10 -0
- package/dist/examples/mcp-lafs-client.js +427 -0
- package/dist/examples/mcp-lafs-server.d.ts +10 -0
- package/dist/examples/mcp-lafs-server.js +358 -0
- package/dist/schemas/v1/envelope.schema.json +103 -14
- package/dist/schemas/v1/error-registry.json +19 -1
- package/dist/src/budgetEnforcement.d.ts +84 -0
- package/dist/src/budgetEnforcement.js +328 -0
- package/dist/src/cli.d.ts +14 -0
- package/dist/src/cli.js +14 -0
- package/dist/src/conformance.js +80 -7
- package/dist/src/discovery.d.ts +127 -0
- package/dist/src/discovery.js +304 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/mcpAdapter.d.ts +28 -0
- package/dist/src/mcpAdapter.js +281 -0
- package/dist/src/tokenEstimator.d.ts +87 -0
- package/dist/src/tokenEstimator.js +238 -0
- package/dist/src/types.d.ts +67 -7
- package/lafs.md +331 -23
- package/package.json +8 -3
- package/schemas/v1/context-ledger.schema.json +70 -0
- package/schemas/v1/discovery.schema.json +132 -0
- package/schemas/v1/envelope.schema.json +103 -14
- package/schemas/v1/error-registry.json +19 -1
package/README.md
CHANGED
|
@@ -1,45 +1,151 @@
|
|
|
1
|
-
#
|
|
1
|
+
# LAFS Protocol
|
|
2
2
|
|
|
3
|
-
LLM-Agent-First Specification
|
|
3
|
+
**LLM-Agent-First Specification** — a response envelope contract for AI agent systems.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
LAFS defines a standard envelope format for structured responses from LLM-powered agents and tools. It complements transport protocols like [MCP](https://modelcontextprotocol.io/) and [A2A](https://github.com/google/A2A) by standardizing what comes back — not how it gets there.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Current version:** 0.5.0 | [Spec](lafs.md) | [Migration Guides](migrations/)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
## What LAFS provides
|
|
10
|
+
|
|
11
|
+
| Layer | Files | Description |
|
|
12
|
+
|-------|-------|-------------|
|
|
13
|
+
| **Spec** | `lafs.md` | Protocol specification with RFC 2119 language |
|
|
14
|
+
| **Schemas** | `schemas/v1/envelope.schema.json` | Envelope schema (Draft-07) with conditional pagination validation |
|
|
15
|
+
| | `schemas/v1/context-ledger.schema.json` | Context ledger for state tracking across request/response cycles |
|
|
16
|
+
| | `schemas/v1/error-registry.json` | 12 registered error codes with HTTP/gRPC/CLI transport mappings |
|
|
17
|
+
| **Tooling** | `src/` | TypeScript validation, conformance runner, CLI diagnostic tool |
|
|
18
|
+
| **Tests** | `tests/` | 31 tests covering envelope, pagination, strict mode, error handling |
|
|
19
|
+
| **Fixtures** | `fixtures/` | 14 JSON fixtures (valid + invalid) for conformance testing |
|
|
20
|
+
| **Docs** | `docs/` | Positioning, vision, conformance tiers, deprecation policy |
|
|
14
21
|
|
|
15
22
|
## Install
|
|
16
23
|
|
|
17
24
|
```bash
|
|
18
|
-
npm install
|
|
25
|
+
npm install @cleocode/lafs-protocol
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import {
|
|
32
|
+
validateEnvelope,
|
|
33
|
+
runEnvelopeConformance,
|
|
34
|
+
isRegisteredErrorCode,
|
|
35
|
+
} from "@cleocode/lafs-protocol";
|
|
36
|
+
|
|
37
|
+
// Validate an envelope against the schema
|
|
38
|
+
const result = validateEnvelope(envelope);
|
|
39
|
+
if (!result.valid) {
|
|
40
|
+
console.error(result.errors);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Run full conformance suite (schema + invariants + error codes + strict mode + pagination)
|
|
44
|
+
const report = runEnvelopeConformance(envelope);
|
|
45
|
+
console.log(report.ok); // true if all checks pass
|
|
19
46
|
```
|
|
20
47
|
|
|
21
|
-
##
|
|
48
|
+
## CLI
|
|
22
49
|
|
|
23
50
|
```bash
|
|
24
|
-
|
|
51
|
+
# Run conformance checks on a fixture
|
|
52
|
+
npm run conformance -- --envelope fixtures/valid-success-envelope.json
|
|
53
|
+
|
|
54
|
+
# Run tests
|
|
25
55
|
npm test
|
|
26
|
-
|
|
56
|
+
|
|
57
|
+
# Type check
|
|
58
|
+
npm run typecheck
|
|
27
59
|
```
|
|
28
60
|
|
|
29
|
-
##
|
|
61
|
+
## Envelope structure
|
|
30
62
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"$schema": "https://lafs.dev/schemas/v1/envelope.schema.json",
|
|
66
|
+
"_meta": {
|
|
67
|
+
"specVersion": "0.5.0",
|
|
68
|
+
"schemaVersion": "1.0.0",
|
|
69
|
+
"timestamp": "2026-02-13T00:00:00Z",
|
|
70
|
+
"operation": "example.list",
|
|
71
|
+
"requestId": "req_01",
|
|
72
|
+
"transport": "http",
|
|
73
|
+
"strict": true,
|
|
74
|
+
"mvi": "standard",
|
|
75
|
+
"contextVersion": 1
|
|
76
|
+
},
|
|
77
|
+
"success": true,
|
|
78
|
+
"result": { "items": [{ "id": "1", "title": "Example" }] },
|
|
79
|
+
"page": {
|
|
80
|
+
"mode": "cursor",
|
|
81
|
+
"nextCursor": "eyJpZCI6IjEwIn0=",
|
|
82
|
+
"hasMore": true
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Key features
|
|
88
|
+
|
|
89
|
+
- **Conditional pagination** — cursor, offset, and none modes with mode-specific required fields
|
|
90
|
+
- **Strict/lenient mode** — `strict: true` rejects unknown properties; `strict: false` allows them
|
|
91
|
+
- **MVI disclosure levels** — `minimal`, `standard`, `full`, `custom` control response verbosity
|
|
92
|
+
- **Field selection** (`_fields`) and **expansion** (`_expand`) request parameters
|
|
93
|
+
- **Context ledger** — tracks state across request/response cycles with monotonic versioning
|
|
94
|
+
- **Error registry** — 12 codes with category, retryability, and transport-specific status mappings
|
|
95
|
+
- **Extension mechanism** — `_extensions` field for vendor metadata (`x-` prefix convention)
|
|
96
|
+
- **Adoption tiers** — Core, Standard, Complete with progressive conformance requirements
|
|
97
|
+
|
|
98
|
+
## Conformance checks
|
|
99
|
+
|
|
100
|
+
| Check | Description | Tier |
|
|
101
|
+
|-------|-------------|------|
|
|
102
|
+
| `envelope_schema_valid` | Validates against JSON Schema | Core |
|
|
103
|
+
| `envelope_invariants` | success/result/error consistency | Core |
|
|
104
|
+
| `error_code_registered` | Error code exists in registry | Core |
|
|
105
|
+
| `meta_mvi_present` | Valid MVI disclosure level | Standard |
|
|
106
|
+
| `meta_strict_present` | Strict mode declared | Standard |
|
|
107
|
+
| `strict_mode_behavior` | Optional fields omitted (not null) in strict mode | Standard |
|
|
108
|
+
| `strict_mode_enforced` | Additional properties rejected/allowed per mode | Standard |
|
|
109
|
+
| `pagination_mode_consistent` | Page fields match declared mode | Standard |
|
|
35
110
|
|
|
36
|
-
##
|
|
111
|
+
## Project layout
|
|
37
112
|
|
|
38
|
-
```
|
|
39
|
-
lafs.md
|
|
113
|
+
```
|
|
114
|
+
lafs.md # Protocol specification
|
|
40
115
|
schemas/v1/
|
|
116
|
+
envelope.schema.json # Envelope schema (Draft-07)
|
|
117
|
+
context-ledger.schema.json # Context ledger schema
|
|
118
|
+
error-registry.json # Error code registry
|
|
41
119
|
src/
|
|
42
|
-
|
|
43
|
-
|
|
120
|
+
types.ts # TypeScript types (discriminated unions)
|
|
121
|
+
validateEnvelope.ts # Ajv-based schema validator
|
|
122
|
+
conformance.ts # Conformance runner (8 checks)
|
|
123
|
+
errorRegistry.ts # Error code helpers
|
|
124
|
+
flagSemantics.ts # Format flag resolution
|
|
125
|
+
cli.ts # CLI diagnostic tool
|
|
126
|
+
tests/ # 31 tests (vitest)
|
|
127
|
+
fixtures/ # 14 JSON test fixtures
|
|
44
128
|
docs/
|
|
129
|
+
POSITIONING.md # MCP/A2A complementary positioning
|
|
130
|
+
VISION.md # Project vision and primary persona
|
|
131
|
+
CONFORMANCE.md # Conformance checks and adoption tiers
|
|
132
|
+
migrations/
|
|
133
|
+
v0.3.0-to-v0.4.0.md # Envelope rationalization migration
|
|
134
|
+
v0.4.0-to-v0.5.0.md # Pagination & MVI schema migration
|
|
135
|
+
CONTRIBUTING.md # Contributor guidelines, RFC process
|
|
45
136
|
```
|
|
137
|
+
|
|
138
|
+
## Version history
|
|
139
|
+
|
|
140
|
+
| Version | Phase | Description |
|
|
141
|
+
|---------|-------|-------------|
|
|
142
|
+
| v0.5.0 | 2B | Conditional pagination, MVI field selection/expansion, context ledger schema |
|
|
143
|
+
| v0.4.0 | 2A | Optional page/error, extensions, strict/lenient mode, warnings |
|
|
144
|
+
| v0.3.0 | 1 | Strategic positioning, vision alignment, adoption tiers |
|
|
145
|
+
| v0.2.0 | 0 | Protocol cleanup, fixtures, governance, security considerations |
|
|
146
|
+
| v0.1.1 | — | Initial npm publish |
|
|
147
|
+
| v0.1.0 | — | Bootstrap |
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
|
@@ -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 {};
|