@cyanmycelium/mcp-persistence 0.0.0-stage → 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.
Files changed (46) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +189 -2
  3. package/contracts/persistence.v1.json +9606 -0
  4. package/dist/conformance/persistence.conformance.d.ts +4 -0
  5. package/dist/conformance/persistence.conformance.js +56 -0
  6. package/dist/conformance/persistence.conformance.js.map +1 -0
  7. package/dist/contract/errors.d.ts +5 -0
  8. package/dist/contract/errors.js +9 -0
  9. package/dist/contract/errors.js.map +1 -0
  10. package/dist/contract/persistence.protocol.d.ts +83 -0
  11. package/dist/contract/persistence.protocol.js +75 -0
  12. package/dist/contract/persistence.protocol.js.map +1 -0
  13. package/dist/contract/persistence.store.d.ts +29 -0
  14. package/dist/contract/persistence.store.js +2 -0
  15. package/dist/contract/persistence.store.js.map +1 -0
  16. package/dist/contract/persistence.types.d.ts +115 -0
  17. package/dist/contract/persistence.types.js +2 -0
  18. package/dist/contract/persistence.types.js.map +1 -0
  19. package/dist/contract/validation.d.ts +7 -0
  20. package/dist/contract/validation.js +43 -0
  21. package/dist/contract/validation.js.map +1 -0
  22. package/dist/index.d.ts +9 -0
  23. package/dist/index.js +10 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/provider/artifact.http.d.ts +8 -0
  26. package/dist/provider/artifact.http.js +69 -0
  27. package/dist/provider/artifact.http.js.map +1 -0
  28. package/dist/provider/main.d.ts +2 -0
  29. package/dist/provider/main.js +82 -0
  30. package/dist/provider/main.js.map +1 -0
  31. package/dist/server/declaration.d.ts +18 -0
  32. package/dist/server/declaration.js +23 -0
  33. package/dist/server/declaration.js.map +1 -0
  34. package/dist/server/persistence.behavior.d.ts +16 -0
  35. package/dist/server/persistence.behavior.js +173 -0
  36. package/dist/server/persistence.behavior.js.map +1 -0
  37. package/dist/server/persistence.slot.store.d.ts +45 -0
  38. package/dist/server/persistence.slot.store.js +94 -0
  39. package/dist/server/persistence.slot.store.js.map +1 -0
  40. package/dist/sqlite/sqlite.persistence.store.d.ts +72 -0
  41. package/dist/sqlite/sqlite.persistence.store.js +561 -0
  42. package/dist/sqlite/sqlite.persistence.store.js.map +1 -0
  43. package/docs/assets/logo.png +0 -0
  44. package/docs/assets/mcp-broker-family.png +0 -0
  45. package/docs/mcp-contract.md +189 -0
  46. package/package.json +79 -4
@@ -0,0 +1,189 @@
1
+ # Contrat MCP persistence.v1, révision 1
2
+
3
+ Ce document et [contracts/persistence.v1.json](../contracts/persistence.v1.json) constituent le contrat pour développer les clients indépendamment du backend. Le manifeste JSON fournit les schémas complets `inputSchema` et `outputSchema` de `tools/list`, les ressources, templates et routes de transfert. Les définitions TypeScript se trouvent dans [src/contract/](../src/contract/).
4
+
5
+ Source unique des descripteurs : `src/contract/persistence.protocol.ts`, utilisée directement par `PersistenceBehavior`. `npm run contracts` régénère le manifeste. Les tests comparent les outils publiés aux définitions et vérifient les appels représentatifs. Une rupture de forme ou de sémantique exige une nouvelle version de contrat ; les ajouts compatibles doivent être explicitement révisés.
6
+
7
+ ## Connexion et enveloppes
8
+
9
+ - Client MCP : `http(s)://<broker>/persistence/mcp`, transport MCP Streamable HTTP standard.
10
+ - Provider : `ws(s)://<broker>/provider/persistence`, DirectTransport.
11
+ - Transfert binaire : origine HTTPS configurée séparément côté client. En dev local : `http://127.0.0.1:3001`.
12
+ - Authentification : bearer client sur MCP et HTTP. En mode local explicite `--dev`, aucun jeton n'est requis et openGuard accepte les contrôles métier.
13
+
14
+ Les outils s'appellent exactement `persistence.<nom>`. Sur `_all`, découvrir les noms avec `tools/list` ; ne jamais reconstruire un préfixe. Les ressources se lisent sur le slot `persistence`.
15
+
16
+ Appel JSON-RPC :
17
+
18
+ ```json
19
+ {
20
+ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
21
+ "params": {
22
+ "name": "persistence.get",
23
+ "arguments": { "namespace": "explorer", "id": "checkpoint-1" }
24
+ }
25
+ }
26
+ ```
27
+
28
+ Le résultat métier est un objet dans `result.structuredContent`, avec le même JSON dans `result.content[0].text` pour les clients anciens. Lire structuredContent en priorité. Il n'y a pas d'enveloppe métier supplémentaire autour du document ou de la page.
29
+
30
+ Un échec métier utilise `result.isError: true` et `structuredContent: { "error": { "code": "...", "message": "..." } }`. Un refus du broker avant l'outil peut être une erreur JSON-RPC de transport, sans cette enveloppe. Les clients doivent traiter les deux cas. `PersistenceSlotStore` gère les résultats métier ; une erreur de transport peut aussi rejeter la promesse.
31
+
32
+ ## Types communs
33
+
34
+ `namespace`, `id`, `artifactId` et `uploadId` sont des segments ASCII de 1 à 128 caractères, motif `^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$`. Les identifiants d'artefact et d'upload sont générés par le service. Aucun identifiant n'est un chemin disque.
35
+
36
+ `revision` est un entier positif sûr JavaScript. `expectedRevision` accepte également zéro. Les dates sont des chaînes UTC ISO 8601. `schemaVersion` est une chaîne définie par l'application, même pour une version numérique comme `"1"`. Les objets JSON n'acceptent ni valeurs non finies ni cycles ; profondeur maximale 64, complexité maximale 100 000 nœuds.
37
+
38
+ ```ts
39
+ type Reference =
40
+ | { type: "document"; namespace: string; id: string; revision: number }
41
+ | { type: "artifact"; namespace: string; id: string };
42
+
43
+ interface Document {
44
+ namespace: string; id: string;
45
+ kind: string; schemaVersion: string;
46
+ revision: number;
47
+ createdAt: string; updatedAt: string;
48
+ deleted: boolean;
49
+ payload: Json;
50
+ references: Reference[];
51
+ }
52
+
53
+ interface Artifact {
54
+ namespace: string; id: string;
55
+ mimeType: string; size: number;
56
+ hash: { algorithm: "sha256"; value: string }; // 64 caractères hexadécimaux minuscules
57
+ createdAt: string;
58
+ provenance: Json;
59
+ }
60
+
61
+ interface Page<T> { items: T[]; nextCursor?: string }
62
+ interface Upload { namespace: string; artifactId: string; uploadId: string; expiresAt: string }
63
+ ```
64
+
65
+ Les propriétés inconnues sont interdites dans les arguments des outils. `payload` et `provenance` restent des JSON libres. Les schémas complets du manifeste précisent champs requis, bornes et discriminants.
66
+
67
+ ## Outils
68
+
69
+ Tous les résultats ci-dessous désignent structuredContent. `namespace` est toujours requis, sauf pour capabilities. Les champs suivis de `?` sont optionnels.
70
+
71
+ | Outil | Arguments | Résultat |
72
+ |---|---|---|
73
+ | `persistence.capabilities` | `{}` | `{contract:"persistence.v1",durability,limits}` |
74
+ | `persistence.get` | `namespace,id,revision?` | `Document` |
75
+ | `persistence.put` | `namespace,id,kind,schemaVersion,payload,references,expectedRevision,idempotencyKey` | nouvelle `Document` |
76
+ | `persistence.list` | `namespace,kind?,includeDeleted?,limit?,cursor?` | `Page<Document>` |
77
+ | `persistence.listRevisions` | `namespace,id,limit?,cursor?` | `Page<Document>` |
78
+ | `persistence.delete` | `namespace,id,expectedRevision,idempotencyKey` | `Document` tombstone |
79
+ | `persistence.purge` | `namespace,target,idempotencyKey` | `{ok:true}` |
80
+ | `persistence.beginArtifact` | `namespace,mimeType,size,hash,provenance,idempotencyKey` | `Upload` |
81
+ | `persistence.uploadAccess` | `namespace,uploadId` | `Upload`, après autorisation d'écriture |
82
+ | `persistence.commitArtifact` | `namespace,uploadId,idempotencyKey` | `Artifact` |
83
+ | `persistence.abortArtifact` | `namespace,uploadId,idempotencyKey` | `{ok:true}` |
84
+ | `persistence.getArtifact` | `namespace,id` | `Artifact`, sans contenu binaire |
85
+
86
+ `purge.target` vaut `{type:"document",id,revision}` ou `{type:"artifact",id}`. La tête courante d'un document ne peut pas être purgée. L'assemblage CLI garde une rétention infinie, donc la purge y reste désactivée même avec un droit admin ; un assemblage bibliothèque peut définir une durée finie.
87
+
88
+ `capabilities.limits` contient `maxDocumentBytes`, `maxArtifactBytes`, `maxReferences`, `maxPageSize`, `maxNamespaceBytes`, `idempotencyRetentionSeconds`. `durability` vaut `file-and-directory-sync`, ou `file-sync` avec l'option explicite correspondante. Lire ces limites au démarrage ; ne pas les déduire du nom du backend.
89
+
90
+ ## Sauvegarder un checkpoint
91
+
92
+ Arguments de `persistence.put` :
93
+
94
+ ```json
95
+ {
96
+ "namespace": "explorer", "id": "checkpoint-1",
97
+ "kind": "checkpoint", "schemaVersion": "1",
98
+ "expectedRevision": 0, "idempotencyKey": "c4bc60e6-3740-44ed-8f4c-bb6b885ea247",
99
+ "payload": {
100
+ "sessionId": "session-1", "astronomicalDate": "2042-01-01T00:00:00Z",
101
+ "body": "Earth", "frame": "ICRF",
102
+ "camera": { "position": [0, 0, 10], "target": [0, 0, 0] },
103
+ "projection": "perspective", "layers": [], "objects": [], "selection": [], "dataVersions": []
104
+ },
105
+ "references": []
106
+ }
107
+ ```
108
+
109
+ Zéro signifie créer uniquement. Pour modifier, garder namespace et id et transmettre la dernière révision connue comme expectedRevision. Deux mises à jour sur la même révision donnent un succès et un conflit ; ne pas réessayer un conflit en remplaçant aveuglément la révision.
110
+
111
+ Après perte de connexion, réessayer la même mutation avec le même idempotencyKey et exactement le même contenu et les mêmes préconditions. Un contenu différent avec la même clé produit `idempotency_conflict`. Pour MCP, les clés sont communes au namespace, conservées au moins sept jours. Utiliser des UUID par opération, partagés entre tentatives. Ne pas inclure une identité utilisateur dans les arguments : le broker la détermine sur le transport.
112
+
113
+ La suppression logique ajoute une nouvelle révision tombstone. get sans revision renvoie ensuite not_found ; get avec une ancienne revision reste utilisable avec les droits requis. Une restauration explicite écrit une nouvelle révision en passant le numéro du tombstone, sans écraser l'historique.
114
+
115
+ ## Documents de mission et preuves
116
+
117
+ Pour citer une vue et une capture dans un document de mission :
118
+
119
+ ```json
120
+ [
121
+ { "type": "document", "namespace": "explorer", "id": "view-1", "revision": 3 },
122
+ { "type": "artifact", "namespace": "explorer", "id": "1097cc30-2a56-4d17-a1e8-dbe9eb315529" }
123
+ ]
124
+ ```
125
+
126
+ Ce tableau va dans references. Les cibles doivent déjà exister et être lisibles. Un artefact doit être engagé. L'évolution ultérieure de la vue ne change pas la révision 3. Une référence entrante empêche la purge de sa cible ; supprimer logiquement la tête d'une mission ne supprime pas ses anciennes références historiques.
127
+
128
+ ## Transférer un fichier
129
+
130
+ 1. Calculer la taille et SHA-256 côté client.
131
+ 2. Appeler beginArtifact avec les métadonnées et une clé d'idempotence. Conserver uploadId, artifactId et expiresAt.
132
+ 3. Envoyer un `PUT /uploads/<namespace>/<uploadId>` sur l'origine de transfert, avec le bearer client et le fichier comme corps HTTP brut. Le serveur relaie le bearer au broker et vérifie persistence.write sur la cible avant d'ouvrir le flux. Succès : HTTP 204. Aucun base64 dans MCP.
133
+ 4. Appeler commitArtifact avec namespace, uploadId et une autre clé d'idempotence. L'outil vérifie le transfert complet et l'intégrité avant publication. Succès : métadonnées Artifact.
134
+ 5. Seul cet artefact engagé peut être référencé par un document.
135
+
136
+ L'upload expire après 24 heures. Après interruption du PUT, réessayer le fichier depuis le début sur le même upload encore pending, ou annuler puis en créer un nouveau. Après un PUT réussi, ne pas le renvoyer : engager l'upload. Après perte de réponse au commit, rejouer le même commit et la même clé. Il n'y a pas de reprise par plages.
137
+
138
+ Pour télécharger, getArtifact donne les métadonnées. Envoyer ensuite `GET /artifacts/<namespace>/<id>` avec le bearer client. Succès HTTP 200, Content-Type enregistré, Content-Length exact, octets bruts et Cache-Control no-store. Vérifier le SHA-256 reçu dans l'application si nécessaire. Aucun ticket public ni chemin physique n'est retourné. Une nouvelle requête refait une décision ; un flux déjà ouvert n'est pas révoqué au milieu.
139
+
140
+ Erreurs HTTP : 400 pour requête, taille incomplète ou intégrité ; 403 pour accès refusé ; 404 pour cible absente ; 413 pour quota ; 500 pour stockage indisponible ou occupé. Le corps contient `{error:{code,message}}`. Une route ou méthode inconnue renvoie 404 sans obligation de corps JSON. Ne pas suivre de redirection avec le bearer.
141
+
142
+ ## Listes et ressources
143
+
144
+ list ordonne par id, listRevisions par revision croissante. limit vaut 50 par défaut, au maximum 200, sous réserve de capabilities. Réutiliser les mêmes filtres avec nextCursor, ne jamais le décoder ni le modifier. Les curseurs sont chiffrés et authentifiés, valables quinze minutes. La liste peut évoluer entre deux pages.
145
+
146
+ Le filtrage d'autorisation s'applique avant exposition au client, mais après la pagination interne. Une page autorisée peut donc être vide avec un nextCursor ; continuer tant que ce champ existe. Aucun total global n'est exposé.
147
+
148
+ Ressources de type application/json :
149
+
150
+ - `persistence://capabilities`, limites publiques ;
151
+ - `persistence://documents/<namespace>/<id>`, tête ;
152
+ - `persistence://documents/<namespace>/<id>/revisions/<revision>`, révision précise ;
153
+ - `persistence://artifacts/<namespace>/<id>`, métadonnées d'artefact.
154
+
155
+ resources/read renvoie le JSON dans `contents[].text`, suivant l'enveloppe MCP standard. Les mêmes droits métier sont vérifiés que pour les outils. Aucun abonnement ni notification de changement des documents n'est promis dans cette révision du contrat.
156
+
157
+ ## Erreurs et accès
158
+
159
+ | Code | Action client |
160
+ |---|---|
161
+ | `not_found` | cible absente, supprimée logiquement ou upload expiré |
162
+ | `revision_conflict` | relire, comparer et résoudre le conflit métier |
163
+ | `idempotency_conflict` | corriger la réutilisation d'une clé |
164
+ | `policy_denied` | revoir l'identité et les droits, sans retry automatique |
165
+ | `quota_exceeded` | réduire le volume ou demander un quota adapté |
166
+ | `transfer_incomplete` | terminer ou recommencer le transfert avant commit |
167
+ | `integrity_error` | ne pas publier/restaurer le contenu, vérifier les hashes |
168
+ | `invalid_request` | corriger les arguments selon le manifeste |
169
+ | `unsupported_format` | utiliser une fonctionnalité ou un stockage pris en charge |
170
+ | `retention_blocked` | conserver la cible ou retirer les références historiques par une procédure autorisée |
171
+ | `busy` | réessayer avec délai borné et même clé pour une mutation |
172
+ | `store_unavailable` | vérifier le service ; après réponse perdue, garder la même clé |
173
+
174
+ La politique distingue persistence.read, write, delete et admin. Pour la création d'artefact, write se vérifie sur `<root>/namespaces/<namespace>/artifacts/_new`, puis sur l'identifiant généré pour le transfert et le commit. La déclaration ne donne aucun droit. Les appels non autorisés sont refusés avant de révéler l'existence d'une cible connue par son identifiant.
175
+
176
+ ## Développer sans attendre le backend
177
+
178
+ Importer les types et `PERSISTENCE_PROTOCOL` depuis le paquet compilé ou charger le manifeste JSON pour créer des fixtures de serveur. Les descripteurs sont indépendants de SQLite. Pour un mock de transport, implémenter ISlotClient.callTool, renvoyer les objets structuredContent ci-dessus et injecter ce client dans PersistenceSlotStore.
179
+
180
+ ```ts
181
+ const client = {
182
+ async callTool(name: string, args: Record<string, unknown>) {
183
+ return { content: [], structuredContent: fixtures[name](args) };
184
+ }
185
+ };
186
+ const persistence = new PersistenceSlotStore("persistence", client);
187
+ ```
188
+
189
+ Ce fragment illustre l'injection ; fixtures appartient aux tests de l'application. Pour les binaires, fournir une origine HTTP de test avec les deux routes décrites ci-dessus. Les mocks doivent simuler les conflits, les erreurs et les réponses perdues, pas seulement les succès. L'exemple exécutable Explorer se trouve dans `examples/explorer.mjs`.
package/package.json CHANGED
@@ -1,6 +1,81 @@
1
1
  {
2
2
  "name": "@cyanmycelium/mcp-persistence",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Durable versioned documents and artifacts, published through an MCP broker slot.",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "engines": {
8
+ "node": ">=22.13.0"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./conformance": {
16
+ "types": "./dist/conformance/persistence.conformance.d.ts",
17
+ "import": "./dist/conformance/persistence.conformance.js"
18
+ },
19
+ "./protocol": {
20
+ "types": "./dist/contract/persistence.protocol.d.ts",
21
+ "import": "./dist/contract/persistence.protocol.js"
22
+ },
23
+ "./contracts/persistence.v1.json": "./contracts/persistence.v1.json"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "contracts",
28
+ "docs/mcp-contract.md",
29
+ "docs/assets/logo.png",
30
+ "docs/assets/mcp-broker-family.png"
31
+ ],
32
+ "scripts": {
33
+ "typecheck": "tsc -p tsconfig.check.json",
34
+ "build": "tsc -p tsconfig.json",
35
+ "test": "npm run build && vitest run",
36
+ "start": "node dist/provider/main.js",
37
+ "dev": "node dist/provider/main.js --dev",
38
+ "contracts": "npm run build && node scripts/export-contract.mjs",
39
+ "prepublishOnly": "npm run typecheck && npm test && npm run contracts"
40
+ },
41
+ "dependencies": {
42
+ "@cyanmycelium/mcp-broker-provider": "0.4.1",
43
+ "@cyanmycelium/mcp-core": "1.4.1",
44
+ "@cyanmycelium/mcp-uns": "0.1.0"
45
+ },
46
+ "devDependencies": {
47
+ "@cyanmycelium/mcp-broker": "1.6.1",
48
+ "@types/node": "^22.0.0",
49
+ "ajv": "^8.17.1",
50
+ "ajv-formats": "^3.0.1",
51
+ "typescript": "5.9.3",
52
+ "vitest": "4.1.11"
53
+ },
54
+ "author": "Guillaume Pelletier (CyanMycelium)",
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/pandaGaume/mcp-persistence.git"
58
+ },
59
+ "homepage": "https://github.com/pandaGaume/mcp-persistence#readme",
60
+ "bugs": {
61
+ "url": "https://github.com/pandaGaume/mcp-persistence/issues"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ },
66
+ "bin": {
67
+ "mcp-persistence": "dist/provider/main.js"
68
+ },
69
+ "peerDependencies": {
70
+ "vitest": "^4.1.11",
71
+ "@cyanmycelium/mcp-broker": ">=1.6.1 <2"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "vitest": {
75
+ "optional": true
76
+ },
77
+ "@cyanmycelium/mcp-broker": {
78
+ "optional": true
79
+ }
80
+ }
81
+ }