@vantageos/mcp-boilerplate 0.1.2 → 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 (50) hide show
  1. package/.claude-plugin/plugin.json +16 -8
  2. package/.mcp.json +8 -0
  3. package/CHANGELOG.md +55 -1
  4. package/README.fr.md +177 -142
  5. package/README.md +170 -133
  6. package/dist/auth.d.ts +20 -10
  7. package/dist/auth.js +9 -8
  8. package/dist/config.d.ts +10 -3
  9. package/dist/config.js +31 -3
  10. package/dist/exampleItems.d.ts +5 -4
  11. package/dist/exampleItems.js +3 -0
  12. package/dist/http.js +11 -1
  13. package/dist/httpApp.d.ts +23 -30
  14. package/dist/httpApp.js +73 -74
  15. package/dist/i18n.d.ts +6 -0
  16. package/dist/i18n.js +9 -0
  17. package/dist/locales/en.json +9 -0
  18. package/dist/locales/fr.json +9 -0
  19. package/dist/mcpServerFactory.d.ts +42 -41
  20. package/dist/mcpServerFactory.js +114 -165
  21. package/dist/oauth/authorizationServer.d.ts +56 -0
  22. package/dist/oauth/authorizationServer.js +423 -0
  23. package/dist/oauth/clerk.d.ts +15 -0
  24. package/dist/oauth/clerk.js +59 -0
  25. package/dist/oauth/clientRegistry.d.ts +50 -0
  26. package/dist/oauth/clientRegistry.js +203 -0
  27. package/dist/oauth/codeStore.d.ts +11 -0
  28. package/dist/oauth/codeStore.js +28 -0
  29. package/dist/oauth/picker.d.ts +11 -0
  30. package/dist/oauth/picker.js +67 -0
  31. package/dist/oauth/settings.d.ts +48 -0
  32. package/dist/oauth/settings.js +100 -0
  33. package/dist/oauth/tokenIssuer.d.ts +22 -0
  34. package/dist/oauth/tokenIssuer.js +61 -0
  35. package/dist/stdio.d.ts +7 -6
  36. package/dist/stdio.js +20 -19
  37. package/dist/tenancy.d.ts +16 -21
  38. package/dist/tenancy.js +12 -26
  39. package/dist/ui/uiResource.d.ts +36 -3
  40. package/dist/ui/uiResource.js +40 -6
  41. package/dist/version.d.ts +2 -0
  42. package/dist/version.js +8 -0
  43. package/docs/architecture.md +82 -0
  44. package/docs/oauth-stores.md +60 -0
  45. package/examples/oauth.env.example +25 -0
  46. package/mcp.json +19 -0
  47. package/package.json +22 -13
  48. package/plugin.json +43 -0
  49. package/scripts/plugin-manifest.source.json +35 -0
  50. package/skills/connect-mcp-server/SKILL.md +40 -0
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "vantage-mcp-boilerplate",
3
- "version": "0.1.2",
4
- "description": "Clonable dual-transport MCP server template (stdio + Streamable HTTP, stateless) on the official @modelcontextprotocol/sdk, with protocol-kit result types, OAuth 2.1 auth + public tier, multi-tenant JWT scoping, MCP Apps UI extension, server-side FR/EN i18n, and a declarative eval harness. Scaffold, extend, publish.",
5
- "description_fr": "Template de serveur MCP clonable a double transport (stdio + Streamable HTTP, stateless) sur le @modelcontextprotocol/sdk officiel, avec types de resultats protocol-kit, auth OAuth 2.1 + tier public, scoping multi-tenant par JWT, extension UI MCP Apps, i18n FR/EN cote serveur, et un harnais d'evaluations declaratif. Scaffolder, etendre, publier.",
6
- "author": "ElPi Corp / Laurent Perello",
3
+ "displayName": "VantageOS MCP Boilerplate",
4
+ "version": "0.2.0",
5
+ "description": "Clonable MCP server template: SDK v2 on protocol 2026-07-28 (stdio + stateless Streamable HTTP), its own OAuth 2.1 authorization server (Clerk person + organisation), MCP Apps UI, FR/EN i18n and a declarative eval harness. Scaffold, extend, publish.",
6
+ "author": {
7
+ "name": "ElPi Corp / Laurent Perello"
8
+ },
9
+ "homepage": "https://github.com/elpiarthera/vantage-mcp-boilerplate#readme",
10
+ "repository": "https://github.com/elpiarthera/vantage-mcp-boilerplate",
7
11
  "license": "MIT",
8
- "homepage": "https://github.com/elpiarthera/vantage-mcp-boilerplate",
9
- "category": "mcp-server",
10
- "bilingual": true,
11
12
  "keywords": [
12
13
  "mcp",
13
14
  "claude-code",
15
+ "chatgpt",
14
16
  "boilerplate",
15
17
  "starter",
16
18
  "template",
@@ -18,8 +20,14 @@
18
20
  "streamable-http",
19
21
  "oauth",
20
22
  "multi-tenant",
23
+ "mcp-apps",
21
24
  "i18n",
22
25
  "vantage",
23
26
  "elpi"
24
- ]
27
+ ],
28
+ "metadata": {
29
+ "description_fr": "Template de serveur MCP clonable : SDK v2 sur le protocole 2026-07-28 (stdio + Streamable HTTP sans état), serveur d'autorisation OAuth 2.1 propre (personne + organisation Clerk), UI MCP Apps, i18n FR/EN et un harnais d'évaluations déclaratif. Scaffolder, étendre, publier.",
30
+ "category": "mcp-server",
31
+ "bilingual": true
32
+ }
25
33
  }
package/.mcp.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "mcp-boilerplate": {
4
+ "type": "http",
5
+ "url": "${MCP_BOILERPLATE_URL:-https://your-server.example.com/mcp}"
6
+ }
7
+ }
8
+ }
package/CHANGELOG.md CHANGED
@@ -7,6 +7,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-10-10
11
+
12
+ SDK v2 / protocol 2026-07-28 migration, own authorization server, MCP Apps + ChatGPT
13
+ metadata, generated packaging manifests. Breaking for forks that import the removed SDK.
14
+
15
+ ### Changed
16
+
17
+ - **BREAKING**: `@modelcontextprotocol/sdk` replaced by `@modelcontextprotocol/server`
18
+ ^2.3.1 (`@modelcontextprotocol/client` ^2.3.1 as a devDependency); Zod ^4.4.3 everywhere
19
+ (the `zod` / `zod/v4` mix is gone); `@modelcontextprotocol/ext-apps` ^2.0.3;
20
+ `@vantageos/cloud-identity` ^0.15.0, now the ONLY identity layer: its resource-server
21
+ entry point (`verifyMcpAccessToken`, `buildProtectedResourceMetadata`) replaces
22
+ `@vantageos/mcp-auth-kit`. `AuthConfig.jwks` is a `{ keys }` JWK set or a function returning
23
+ one (the external-issuer tier fetches and caches it); an unreachable key set answers 503,
24
+ never 401. `jose` remains for minting only.
25
+ - HTTP is `createMcpHandler(factory)` and stdio is `serveStdio(factory)`: one factory serves
26
+ the 2026-07-28 era (`server/discover`) and the 2025 era (`initialize`). The 2025 floor is
27
+ 2025-06-18 (`supportedProtocolVersions`). `ttlMs` / `cacheScope` come from the SDK's
28
+ `cacheHints`. Legacy HTTP responses are now server-sent-event framed.
29
+ - The advertised server version is derived from `package.json` (was the literal `0.1.0`).
30
+ - `tenancy.ts` leaves the deprecated `scopeFilterList` for cloud-identity's `isRowInTenant`:
31
+ rows are selected by their stored `orgId` (added to `ExampleItem`), never by a creator name.
32
+ - `render_items_ui` now refuses an organisation-less caller on the OAuth tier, like
33
+ `list_items` (it used to fall through to the unscoped items).
34
+ - `.claude-plugin/plugin.json`: `description_fr`, `category`, `bilingual` moved under `metadata`.
35
+ - `docs/architecture.md`: wire posture rewritten; the false "no auth here" section removed.
36
+
37
+ ### Added
38
+
39
+ - Own OAuth 2.1 authorization server (`src/oauth/`): `/authorize`, `/authorize/callback`,
40
+ `/authorize/org` (person + organisation picker, FR/EN), `/token` (PKCE S256), `/register`
41
+ (DCR fallback), CIMD client ids (SSRF-guarded), RFC 8414 metadata, `/.well-known/jwks.json`,
42
+ `/userinfo`, RFC 9207 `iss`, PRM at `/.well-known/oauth-protected-resource/mcp`. Decisions
43
+ delegated to cloud-identity; missing configuration answers 503. Code store and client registry
44
+ are interfaces with in-memory adapters (`docs/oauth-stores.md`).
45
+ - Tool records: `title`, the four annotations as explicit booleans, input AND output schemas,
46
+ `structuredContent` plus a same-JSON text block on all three tools.
47
+ `tests/toolRecordComplete.test.ts` fails on any gap.
48
+ - MCP Apps: resource `_meta.ui` (`csp` four domain arrays, `prefersBorder`, `domain` from
49
+ `MCP_UI_DOMAIN`), explicit tool `visibility`, additive `openai/outputTemplate` and
50
+ `openai/toolInvocation/*` keys, the resource-read locale taken from the caller.
51
+ - Packaging generated from one source (`scripts/plugin-manifest.source.json`): `.mcp.json`,
52
+ OpenAI `plugin.json` + `mcp.json`, onboarding skill `skills/connect-mcp-server/SKILL.md`,
53
+ `scripts/generate-signing-key.mjs`, `examples/oauth.env.example`.
54
+
55
+ ### Removed
56
+
57
+ - `@vantageos/mcp-protocol-kit` dependency: the SDK emits `resultType` / cache fields itself.
58
+ - The private `_requestHandlers` access and the hand-rolled `server/discover` request schema.
59
+
60
+ ### Known
61
+
62
+ - Per-organisation API keys for non-ChatGPT clients were never part of this code; not added.
63
+
10
64
  ## [0.1.2] - 2026-08-07
11
65
 
12
66
  Two derive-never-type fixes, released together.
@@ -50,7 +104,7 @@ Initial published release.
50
104
  `@modelcontextprotocol/sdk` transports: stdio (`StdioServerTransport`) and
51
105
  stateless Streamable HTTP (`WebStandardStreamableHTTPServerTransport`).
52
106
  - `@vantageos/mcp-protocol-kit` wiring for shared result types.
53
- - OAuth 2.1 resource-server auth layer (`@vantageos/mcp-auth-kit`), with a
107
+ - OAuth 2.1 resource-server auth layer (since replaced, see 0.2.0), with a
54
108
  public no-auth tier alongside the authenticated tier.
55
109
  - Multi-tenant scoping layer with proven tenant isolation.
56
110
  - MCP Apps UI extension: `ui://` resource serving a bundled widget
package/README.fr.md CHANGED
@@ -1,95 +1,146 @@
1
1
  # @vantageos/mcp-boilerplate
2
2
 
3
- Un squelette de serveur MCP clonable : bootstrap double-transport (stdio +
4
- Streamable HTTP, sans état) construit exclusivement sur les transports
5
- officiels du [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk).
6
- Aucune route REST personnalisée ne se place jamais devant un appel d'outil.
3
+ VENDABLE SOUS : `mcp-boilerplate` — un starter de serveur MCP clonable pour la marketplace Claude Code / le catalogue VantageRegistry et pour ChatGPT, publié sous `@vantageos/mcp-boilerplate` sur npm.
7
4
 
8
- Ceci est une fondation, pas un produit fini : il embarque un outil d'exemple
9
- trivial (`ping`) dont le seul but est de prouver que la poignée de main MCP
10
- fonctionne sur les deux transports. Les couches ultérieures (types de résultat
11
- protocol-kit, authentification, scoping multi-tenant, extensions UI, i18n) se
12
- construisent au-dessus de ce bootstrap sans modifier le câblage du transport.
5
+ Un squelette de serveur MCP clonable sur le SDK officiel
6
+ [`@modelcontextprotocol/server`](https://www.npmjs.com/package/@modelcontextprotocol/server)
7
+ v2 : protocole **2026-07-28** avec la poignée de main 2025 toujours servie, stdio
8
+ et Streamable HTTP sans état depuis une seule définition de serveur, son **propre
9
+ serveur d'autorisation OAuth 2.1** (une personne Clerk choisit une organisation),
10
+ une UI MCP Apps et des messages FR/EN. Aucune route REST personnalisée ne se place
11
+ jamais devant un appel d'outil.
12
+
13
+ Il embarque trois petits outils d'exemple (`ping`, `list_items`, `render_items_ui`)
14
+ qui exercent chaque couche. Remplacez-les par les vôtres ; le câblage reste.
13
15
 
14
16
  ## Structure
15
17
 
16
18
  ```
17
19
  src/
18
20
  mcpServerFactory.ts # définition unique du serveur MCP, partagée par les deux transports
19
- stdio.ts # point d'entrée StdioServerTransport du SDK officiel
21
+ version.ts # nom + version du serveur, dérivés de package.json
22
+ stdio.ts # serveStdio(factory) : les deux ères du protocole sur stdio
20
23
  http.ts # adaptateur Node http.Server autour de l'app Hono
21
- httpApp.ts # WebStandardStreamableHTTPServerTransport du SDK officiel (sans état)
22
- auth.ts # câblage OAuth 2.1 resource-server (@vantageos/mcp-auth-kit)
23
- config.ts # chargeurs AuthMode + AuthConfig dérivés de l'environnement
24
- index.ts # exports du package
25
- tests/
26
- helpers/mcpRpc.ts # helpers de requête JSON-RPC pour le transport HTTP
27
- helpers/authFixtures.ts # fixtures de signature JWT RS256 réservées aux tests
28
- httpHandshake.test.ts # initialize -> tools/list -> tools/call sur Streamable HTTP
29
- stdioHandshake.test.ts # même poignée de main sur un vrai process enfant stdio
30
- oauth.test.ts # les deux paliers d'authentification : gate oauth + palier public sans auth
31
- docs/
32
- architecture.md # posture du protocole : version au niveau outil vs au niveau transport
24
+ httpApp.ts # createMcpHandler(factory) sur /mcp + la barrière OAuth
25
+ auth.ts # câblage resource-server (@vantageos/cloud-identity)
26
+ config.ts # AuthMode + AuthConfig d'émetteur externe, dérivés de l'environnement
27
+ tenancy.ts # JWT vérifié -> tenant ; lignes filtrées par orgId stocké
28
+ oauth/ # serveur d'autorisation propre (cloud-identity décide)
29
+ authorizationServer.ts settings.ts clerk.ts tokenIssuer.ts
30
+ clientRegistry.ts codeStore.ts picker.ts
31
+ ui/ # widget MCP Apps + ressource ui://
32
+ skills/connect-mcp-server/SKILL.md # skill d'onboarding (anglais)
33
+ scripts/
34
+ plugin-manifest.source.json # l'UNIQUE source de tous les manifestes de packaging
35
+ sync-plugin-version.mjs # génère les quatre manifestes (dans `npm run build`)
36
+ generate-signing-key.mjs # affiche une JWK privée RS256 pour MCP_OAUTH_SIGNING_JWK
37
+ tests/ evals/ docs/
33
38
  ```
34
39
 
35
40
  ## Démarrage rapide
36
41
 
37
42
  ```bash
38
43
  npm install
39
- npm run build
44
+ npm run build && npm run build:ui
40
45
 
41
46
  # stdio (config client local, ex. Claude Code / Cursor) — jamais gardé,
42
47
  # le process local qui invoque stdio EST l'appelant.
43
48
  npm run start:stdio
44
49
 
45
- # Streamable HTTP (distant, sans état) — gate OAuth 2.1 par défaut :
46
- MCP_OAUTH_ISSUER=https://your-tenant.example.com \
47
- MCP_OAUTH_AUDIENCE=https://your-mcp-server.example.com \
48
- PORT=8787 npm run start:http
49
-
50
- # Streamable HTTP, palier "Public MCP" sans authentification (local/self-host, appelant de confiance) :
50
+ # Streamable HTTP, palier public (local/self-host, appelant de confiance) :
51
51
  AUTH_MODE=public PORT=8787 npm run start:http
52
+
53
+ # Streamable HTTP, palier OAuth (défaut) : ce serveur est son propre serveur
54
+ # d'autorisation. Voir examples/oauth.env.example pour chaque variable requise.
55
+ node scripts/generate-signing-key.mjs # -> MCP_OAUTH_SIGNING_JWK
56
+ PORT=8787 npm run start:http
52
57
  ```
53
58
 
54
- ## Paliers d'authentification
59
+ ## Protocole et transports
55
60
 
56
- Deux paliers, selon `mcp-standard.md` "Authentication & Authorization" :
61
+ Une seule fabrique (`createBoilerplateMcpServer`) construit un `McpServer` neuf par
62
+ requête (HTTP) ou par connexion (stdio). Le SDK tranche l'ère du protocole :
63
+
64
+ - un client **2026-07-28** ouvre par `server/discover` ; les résultats de
65
+ `tools/list`, `server/discover` et `resources/*` portent `resultType`, `ttlMs` et
66
+ `cacheScope` issus des `cacheHints` du SDK (définis une fois dans la fabrique) ;
67
+ - un client **ère 2025** ouvre par `initialize` et est servi par la branche legacy
68
+ sans état du SDK en HTTP, ou par une connexion épinglée en stdio.
69
+
70
+ Le plancher 2025 est **2025-06-18** : l'option `supportedProtocolVersions` du SDK ne
71
+ liste que `2025-11-25` et `2025-06-18` ; un `initialize` plus ancien se voit
72
+ proposer `2025-11-25`, jamais sa propre version. La version annoncée du serveur est
73
+ lue dans `package.json`.
74
+
75
+ ## Fiches d'outils
76
+
77
+ Chaque outil déclare `title`, les quatre annotations en booléens explicites
78
+ (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`), un schéma
79
+ d'entrée et un schéma de sortie typés (Zod 4), et renvoie `structuredContent` plus
80
+ un bloc texte portant le même JSON. `tests/toolRecordComplete.test.ts` parcourt
81
+ `tools/list` et échoue au moindre manque. Les annotations sont des indications non
82
+ fiables pour le client (mcp-standard C-15) : ne conditionnez jamais une action sur
83
+ elles seules.
84
+
85
+ ## Paliers d'authentification
57
86
 
58
87
  | `AUTH_MODE` | Comportement |
59
88
  |---|---|
60
- | `oauth` (par défaut) | Resource server OAuth 2.1 (`@vantageos/mcp-auth-kit`) : chaque appel HTTP `/mcp` exige un Bearer JWT RS256 valide, lié à l'audience/l'émetteur. Token manquant/invalide -> `401` + `WWW-Authenticate`. Les métadonnées RFC 9728 Protected Resource sont servies sur `/.well-known/oauth-protected-resource`. `/health` reste sans token. Nécessite `MCP_OAUTH_ISSUER` + `MCP_OAUTH_AUDIENCE`. |
61
- | `public` | Aucun gate sur le transport HTTP — pour un usage local/self-host où l'appelant est déjà de confiance. Reprend le modèle de confiance du transport stdio (stdio n'a jamais eu de gate). Aucun document `.well-known/oauth-protected-resource` n'est servi dans ce mode (il n'y a pas de serveur d'autorisation à décrire). |
62
-
63
- Le transport stdio (`stdio.ts`) n'est jamais gardé, dans aucun des deux modes
64
- — le process local qui l'invoque EST l'appelant, selon le modèle de confiance
65
- de `mcp-standard.md` pour les outils locaux/self-host.
66
-
67
- `buildHttpApp()` appelé sans argument revient par défaut au palier `public` —
68
- c'est ce que les tests du squelette de transport (`httpHandshake.test.ts`,
69
- `protocolKitWiring.test.ts`) exercent, donc ils n'ont besoin d'aucune
70
- configuration OAuth. Le point d'entrée de production (`http.ts`) résout
71
- toujours `AUTH_MODE` depuis l'environnement via `config.ts`, avec `oauth`
72
- comme valeur par défaut si non défini.
73
-
74
- ## i18n côté serveur (messages d'erreur FR/EN)
75
-
76
- `src/i18n.ts` localise les messages d'erreur/validation MCP (rejet
77
- d'authentification, refus de tenant, validation d'entrée d'outil) selon le
78
- mandat bilingue de `mcp-standard.md` (§Bilingual) : le client envoie
79
- `X-MCP-Locale: fr` ou `X-MCP-Locale: en` (par défaut `en` si absent), et
80
- `src/httpApp.ts` + `src/mcpServerFactory.ts` acheminent le message
81
- correspondant via `t(locale, key)`.
82
-
83
- Les deux fichiers de ressources (`src/locales/{en,fr}.json`) suivent
84
- exactement la convention de ressource à espace de noms livrée par
85
- `@vantageos/mosaic-i18n@0.1.1`
86
- (`node_modules/@vantageos/mosaic-i18n/src/locales/{en,fr}.json`) : un objet
87
- plat `<Namespace>.<catégorie>.<clé>` -> chaîne, FR et EN maintenus clé pour
88
- clé en parfait alignement. `mosaic-i18n` lui-même est un package
89
- React/`react-i18next` et ne peut pas être appelé directement depuis un
90
- serveur JSON-RPC, donc son API d'exécution n'est pas importée ici — la forme
91
- de son fichier de ressources est réutilisée à la place, sous le namespace
92
- `Mcp` propre à ce boilerplate.
89
+ | `oauth` (défaut) | OAuth 2.1. Sans `MCP_OAUTH_ISSUER`, ce serveur est à la fois serveur d'autorisation et serveur de ressources (ci-dessous). Avec `MCP_OAUTH_ISSUER` + `MCP_OAUTH_AUDIENCE`, il est serveur de ressources seul, pour un émetteur externe. Jeton absent ou invalide -> `401` + `WWW-Authenticate` pointant vers la PRM. |
90
+ | `public` | Aucune barrière en HTTP — local/self-host, appelant de confiance. Aucun document de serveur d'autorisation n'est servi. |
91
+
92
+ stdio n'est jamais gardé, dans aucun des deux modes.
93
+
94
+ ### Serveur d'autorisation propre (arbitrage R1)
95
+
96
+ | Route | Rôle |
97
+ |---|---|
98
+ | `GET /.well-known/oauth-authorization-server` (+ `openid-configuration`) | métadonnées RFC 8414 : S256 seul, `iss` RFC 9207, CIMD et DCR annoncés |
99
+ | `GET /.well-known/oauth-protected-resource/mcp` | PRM RFC 9728 (`resource` = `<base>/mcp`) via cloud-identity |
100
+ | `GET /.well-known/jwks.json` | clés de signature publiques uniquement |
101
+ | `POST /register` | repli Dynamic Client Registration (clients publics, PKCE) |
102
+ | `GET /authorize`, `GET /authorize/callback`, `POST /authorize/org` | connexion Clerk, puis sélecteur personne + organisation avec consentement |
103
+ | `POST /token` | `authorization_code` + PKCE S256 -> JWT RS256 lié à la ressource, à l'émetteur et à l'organisation choisie |
104
+ | `GET /userinfo` | sujet OIDC (et e-mail quand le scope le permet) |
105
+
106
+ Toute décision — contrôles redirect/PKCE/ressource/scope, jeton de consentement,
107
+ liaison et échange du code, formes de la découverte et de userinfo — est prise par
108
+ `@vantageos/cloud-identity` (`startAuthorize`, `resumeAuthorize`,
109
+ `exchangeAuthorizationCode`, `buildDiscoveryDocument`, `buildUserInfo`). Cette app
110
+ fournit l'environnement, les stores, les appels Clerk, la page de sélection et la
111
+ signature. Un client peut s'identifier par l'URL d'un Client ID Metadata Document
112
+ (récupéré avec garde SSRF) ou s'enregistrer via `/register`.
113
+
114
+ **Fail-closed** : une variable manquante (`PUBLIC_BASE_URL`, `CLERK_DOMAIN`,
115
+ `AUTHORIZE_SIGN_IN_URL`, `AUTHORIZE_CALLBACK_URL`, `AUTHORIZE_STATE_SECRET`,
116
+ `CLERK_SECRET_KEY`, `MCP_OAUTH_SIGNING_JWK`) répond `503` et est nommée dans le
117
+ journal (les noms, jamais les valeurs). Il n'existe aucun chemin d'approbation
118
+ automatique.
119
+
120
+ **Stores** : le store des codes d'autorisation et le registre des clients sont des
121
+ interfaces avec adaptateurs en mémoire pour les tests et le développement
122
+ mono-processus. Utilisez des adaptateurs durables en production — contrat dans
123
+ [`docs/oauth-stores.md`](docs/oauth-stores.md).
124
+
125
+ **Non inclus** : jetons de rafraîchissement (les jetons d'accès durent une heure),
126
+ clés d'API par organisation pour les clients hors ChatGPT (ce code n'a jamais eu ce
127
+ chemin ; c'est un manque, pas une régression), URI de redirection à joker, et liste
128
+ de révocation des jetons.
129
+
130
+ ### Multi-tenance
131
+
132
+ `list_items` filtre les lignes par leur `orgId` stocké face à l'organisation
133
+ vérifiée de l'appelant (`isRowInTenant`) ; un jeton sans organisation reçoit un
134
+ refus d'outil explicite, jamais une vue non filtrée. Ne résolvez le tenant de
135
+ l'appelant qu'à partir du jeton vérifié, jamais d'un argument d'outil.
136
+
137
+ ## i18n côté serveur (FR/EN)
138
+
139
+ `src/i18n.ts` localise les messages d'erreur/validation MCP et la page de sélection :
140
+ le client envoie `X-MCP-Locale: fr|en` (défaut `en`) ; le sélecteur navigateur suit
141
+ `Accept-Language`. Les fichiers de ressources (`src/locales/{en,fr}.json`) gardent la
142
+ convention `<Namespace>.<category>.<key>` de `@vantageos/mosaic-i18n`, FR et EN en
143
+ parité.
93
144
 
94
145
  ## Tests
95
146
 
@@ -97,97 +148,81 @@ de son fichier de ressources est réutilisée à la place, sous le namespace
97
148
  npm test
98
149
  ```
99
150
 
100
- Exécute les deux transports contre les vrais chemins de code du SDK officiel :
101
- la suite HTTP pilote l'app Hono en mémoire ; la suite stdio lance le binaire
102
- `dist/stdio.js` compilé comme un vrai process enfant via `StdioClientTransport`.
151
+ Exécute les deux transports via les vrais chemins de code du SDK : la suite HTTP
152
+ pilote l'app Hono en processus avec le `Client` du SDK v2 ; la suite stdio lance le
153
+ `dist/stdio.js` construit. `npm run evals` exécute le jeu de cas déclaratif contre
154
+ stdio.
103
155
 
104
156
  ## Ajouter un outil
105
157
 
106
- Enregistrez-le une seule fois dans `src/mcpServerFactory.ts` — il devient
107
- alors disponible automatiquement sur les deux transports. Il n'y a aucune
108
- route REST séparée à ajouter.
158
+ Enregistrez-le une fois dans `src/mcpServerFactory.ts` avec `title`, les quatre
159
+ annotations, `inputSchema` et `outputSchema`, et renvoyez `structuredResult(...)`. Il
160
+ est alors servi sur les deux transports et les deux ères du protocole.
161
+ `tests/toolRecordComplete.test.ts` le garde honnête.
109
162
 
110
163
  ## Packaging & distribution
111
164
 
112
- Ce boilerplate est lui-même un package npm publiable/clonable. Deux
113
- artefacts portent son identité de distribution :
114
-
115
- - `package.json` — le package npm. `files` ne livre que l'arbre `dist/`
116
- compilé (serveur compilé + `dist/locales/` + `dist/ui-bundle/widget.html`),
117
- le `README.md`, la `LICENSE`, le manifeste de plugin, et la config client
118
- d'exemple — jamais `src/`, les tests, ou l'outillage. `main`/`types`/`exports`
119
- pointent vers `dist/index.js` ; le bin `mcp-boilerplate` pointe vers
120
- `dist/stdio.js`. `publishConfig.access = public` est requis pour le scope
121
- `@vantageos`.
122
- - `.claude-plugin/plugin.json` — le manifeste de catalogue Claude Code
123
- marketplace / VantageRegistry, suivant la convention fleet des serveurs MCP
124
- (miroir de `vantage-frameworks-mcp` / `vantage-agent-composer-mcp`) :
125
- `name`, `version`, `description` bilingue + `description_fr`, `author`,
126
- `license`, `homepage`, `category: "mcp-server"`, `bilingual: true`,
127
- `keywords`.
165
+ Quatre manifestes sont **générés** depuis `scripts/plugin-manifest.source.json` et
166
+ `package.json` par `scripts/sync-plugin-version.mjs` (dans `npm run build`). Ne les
167
+ éditez jamais à la main ; `tests/pluginManifests.test.ts` échoue sur toute dérive.
168
+
169
+ | Fichier | Pour |
170
+ |---|---|
171
+ | `.claude-plugin/plugin.json` | marketplace Claude Code ; les champs non standard (`description_fr`, `category`, `bilingual`) sont sous `metadata` |
172
+ | `.mcp.json` | Claude Code : `type: http`, URL seule, aucun secret |
173
+ | `plugin.json` + `mcp.json` | OpenAI Agent Plugins ; `streamable-http`, auth sous `extensions["com.openai"].auth` (OAuth, CIMD préféré, repli DCR) |
174
+
175
+ L'URL du serveur est un placeholder unique dans le fichier source. Fixez-la pour un
176
+ vrai déploiement avec `MCP_SERVER_URL=https://votre-hote/mcp node scripts/sync-plugin-version.mjs`
177
+ (les deux entrées changent), ou laissez `${MCP_BOILERPLATE_URL:-<placeholder>}` de
178
+ Claude être surchargé par l'installateur. Le skill d'onboarding est
179
+ `skills/connect-mcp-server/SKILL.md`, déclaré pour OpenAI en
180
+ `extensions["com.openai"].onboardingSkill`.
128
181
 
129
182
  ### Scaffold → build → publish
130
183
 
131
184
  ```bash
132
- # 1. Scaffold : clonez ce repo (ou le tarball npm) comme squelette de votre serveur.
133
185
  git clone https://github.com/elpiarthera/vantage-mcp-boilerplate.git my-mcp
134
- cd my-mcp && npm install --legacy-peer-deps # voir la note de skew peer-range dev ci-dessous
135
-
136
- # 2. Renommer : définissez "name"/"bin" dans package.json et "name" dans
137
- # .claude-plugin/plugin.json pour votre serveur, puis ajoutez des outils dans src/mcpServerFactory.ts.
138
-
139
- # 3. Build + vérification depuis un arbre propre (ce que CI et prepublishOnly imposent) :
140
- rm -rf dist node_modules/.vite && npm run build && npm test && npm run evals
141
-
142
- # 4. Inspectez le tarball avant publication — confirmez que dist/ est bien livré et que rien ne fuit :
143
- npm pack --dry-run
144
-
145
- # 5. Publiez sur les deux canaux :
146
- npm publish # registre npm (prepublishOnly relance build+test+evals)
147
- # Catalogue VantageRegistry : enregistrez .claude-plugin/plugin.json via
148
- # le skill fleet create-plugin / generate-marketplace (upsert VR).
186
+ cd my-mcp && npm install
187
+ # Renommez "name"/"bin" dans package.json et les noms dans scripts/plugin-manifest.source.json,
188
+ # puis ajoutez vos outils dans src/mcpServerFactory.ts.
189
+ rm -rf dist && npm run build && npm run build:ui && npm test && npm run evals
190
+ npm pack --dry-run # vérifiez que dist/, les manifestes et skills/ sont livrés
191
+ npm publish # prepublishOnly relance build + test + evals
149
192
  ```
150
193
 
151
- `prepublishOnly` relance `build → build:ui → test → evals` pour qu'un
152
- tarball cassé ou contenant uniquement `src` ne puisse jamais atteindre npm.
153
- Les canaux de distribution sont **npm** (le package exécutable) et le
154
- **catalogue VantageRegistry / Claude Code marketplace** (découverte, via
155
- `plugin.json`).
194
+ La publication se prouve par le `dist-tag` du registre, jamais par le code de sortie
195
+ de `npm publish`.
156
196
 
157
197
  ## Couche UI MCP Apps
158
198
 
159
- `render_items_ui` affiche `EXAMPLE_ITEMS` comme un widget interactif via
160
- l'officiel `@modelcontextprotocol/ext-apps/server` (`registerAppTool` +
161
- `registerAppResource`), forme imbriquée GA `_meta.ui.resourceUri` (stable
162
- depuis le 2026-01-26) — jamais la clé plate dépréciée `_meta["ui/resourceUri"]`.
163
- Un repli markdown est toujours présent dans `content[0]`, même pour les
164
- clients qui ne négocient jamais l'extension `io.modelcontextprotocol/ui`.
165
-
166
- Le widget (`src/ui/`) réutilise une famille `MosaicCard` vendorisée
167
- (`src/ui/mosaic-card.tsx` — atomes présentationnels, aucune dépendance
168
- d'exécution) et est empaqueté en un seul fichier HTML autonome
169
- (`vite-plugin-singlefile`, `dist/ui-bundle/widget.html`, ~70 Ko gzippé,
170
- aucun fetch d'exécution externe). `npm run build:ui` produit ce bundle
171
- indépendamment de `npm run build` (tsc), sur un chemin de sortie sans
172
- collision (`dist/ui-bundle/` vs `dist/ui/` de tsc) — les deux étapes de
173
- build écrivent sur des arbres `dist/ui*` d'apparence similaire mais jamais
174
- sur le même répertoire, si bien que `emptyOutDir` côté vite ne peut jamais
175
- supprimer un module serveur compilé par tsc.
176
-
177
- **Hygiène du template** : les atomes `MosaicCard` sont vendorisés
178
- localement plutôt qu'importés depuis `@vantageos/mosaic-blocks`, un paquet
179
- interne sans version stable (non-alpha) publiée sur npm. Une
180
- devDependency de template épinglée sur un tag alpha mouvant casse au
181
- prochain bump alpha ; vendoriser les quatre primitives présentationnelles
182
- réellement utilisées par le widget élimine ce mode de défaillance.
183
-
184
- **Ordre de publication (exigence stricte)** : ce boilerplate dépend de
185
- `@vantageos/mcp-protocol-kit@^0.2.0`. Le câblage de `tools/list` utilise
186
- `buildToolsListResult` du kit, qui existe UNIQUEMENT à partir du kit 0.2.0+.
187
- En semver 0.x, `^0.2.0` n'accepte PAS le 0.1.x, donc un `npm install` frais
188
- depuis le registre résout la version du kit dont le code a réellement
189
- besoin. Par conséquent, **le kit `@vantageos/mcp-protocol-kit` 0.2.0 DOIT
190
- être publié sur npm AVANT que ce boilerplate puisse être installé ou publié
191
- depuis le registre.** C'est un ordre strict, non optionnel : publiez d'abord
192
- le kit, confirmez que 0.2.0 est en ligne sur le registre, puis publiez le
193
- boilerplate.
199
+ `render_items_ui` rend `EXAMPLE_ITEMS` sous forme de widget interactif
200
+ (`io.modelcontextprotocol/ui`, spec 2026-01-26).
201
+
202
+ - L'outil relie sa ressource `ui://` par la clé imbriquée `_meta.ui.resourceUri`
203
+ avec `visibility: ["model", "app"]` explicite. L'outil est enregistré avec
204
+ `server.registerTool`, et non `registerAppTool` d'ext-apps, car ce helper écrit
205
+ aussi la clé plate dépréciée `_meta["ui/resourceUri"]` (mcp-app-standard A-6).
206
+ - La ressource déclare `_meta.ui` : `csp` avec `connectDomains`, `resourceDomains`,
207
+ `frameDomains` et `baseUriDomains` (tous vides — le widget ne fait aucune requête
208
+ réseau, ne charge aucun actif tiers et n'intègre aucune frame), `prefersBorder`, et
209
+ `domain` seulement quand `MCP_UI_DOMAIN` est défini (le format dépend de l'hôte).
210
+ Son type MIME est `text/html;profile=mcp-app`.
211
+ - **Les clés ChatGPT sont additives** : `openai/outputTemplate` (même URI) et les
212
+ chaînes `openai/toolInvocation/invoking|invoked`. Rien ne les lit ; le serveur
213
+ fonctionne sans aucune clé `openai/*`. Les entrées `openai/ui` ne sont pas
214
+ émises — elles sont optionnelles ; ajoutez-les au `_meta` de l'outil dans
215
+ `src/ui/uiResource.ts` si votre app ChatGPT en a besoin.
216
+ - Un repli markdown est toujours présent dans le résultat d'appel, pour les clients
217
+ qui ne négocient jamais l'extension.
218
+
219
+ Le widget (`src/ui/`) réutilise une famille `MosaicCard` vendorisée et est empaqueté
220
+ en un seul fichier HTML autonome (`vite-plugin-singlefile`,
221
+ `dist/ui-bundle/widget.html`) par `npm run build:ui`, sur un chemin qui n'entre
222
+ jamais en collision avec `dist/ui/` de tsc.
223
+
224
+ **Couche d'identité** : `@vantageos/cloud-identity` (^0.15.0) est le seul paquet
225
+ d'identité. Son point d'entrée resource-server (`verifyMcpAccessToken`,
226
+ `buildProtectedResourceMetadata`) vérifie le jeton Bearer sur `/mcp` ; `jose` ne sert
227
+ plus qu'à signer les jetons d'accès du serveur d'autorisation. Un jeu de clés
228
+ injoignable répond 503, jamais 401.