@vantageos/mcp-boilerplate 0.7.0 → 0.8.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vantage-mcp-boilerplate",
3
3
  "displayName": "VantageOS MCP Boilerplate",
4
- "version": "0.7.0",
4
+ "version": "0.8.0",
5
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
6
  "author": {
7
7
  "name": "ElPi Corp / Laurent Perello"
package/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-11-10
11
+
12
+ ### Added
13
+ - `docs/manufact-gap.md`: the gap list of this boilerplate against every Manufact quality-score check (31 checks, server metadata, discoverability, policy review, domain), each with its verdict, the mcp-doctor rule, the evidence and the file that owns the fix.
14
+ - Server metadata and discoverability (mcp-server-standard S-49, S-50; Manufact "Server Metadata" and "Discoverability"): `serverInfo` serves `title`, `description` (English, or French on `X-MCP-Locale: fr`), `websiteUrl` and a vector icon served at `/icon.svg`, all derived from `package.json` (`displayName`, `description`, `description_fr`, `homepage`) or the `MCP_SERVER_*` variables (`.env.example`); the instructions say how to use the tools instead of pointing to the README; the MCP Server Card is served without credential at `/mcp/server-card`, `/.well-known/mcp/server-card.json`, `/.well-known/mcp-server-card`, `/mcp.json` and `/.well-known/mcp.json`; a browser `GET /mcp` gets a bilingual setup page naming the MCP URL, ahead of the auth gate. Build identity (C-25): `/health` and `serverInfo` `buildId` is now the git commit the tree was built from (`dist/build-info.json`, written by `npm run build`), and the hash of the built files moves to `buildHash`.
15
+ - **The view resource describes itself.** The `ui://` resource carries a bilingual `widgetDescription` (EN / FR) under `_meta.ui.widgetDescription` and `_meta["openai/widgetDescription"]`, the two keys Manufact's *tool-resource-metadata-complete* check reads (T2, docs/manufact-gap.md). The text lives in `src/locales/{en,fr}.json` (`widget.description`).
16
+
17
+ ### Fixed
18
+ - **The package imports from a bundle: locales, version and identity fields are module values, never a file read at import.** `src/i18n.ts` read `locales/{en,fr}.json` and `src/version.ts` read `package.json` when imported, so a bundled consumer (a Convex action importing `@vantageos/lojii-client`, which depends on this package) failed with ENOENT before any code ran. `scripts/embed-static.mjs` now writes `src/embedded.generated.ts` from those files at build (`--check` refuses a stale copy); `getServerIdentity()` reads the embedded package fields too. Proven by importing the built `dist/` from a directory with no `locales/` and no `package.json` (tests/i18n-bundled.test.ts). Declared, unchanged: the server's runtime assets (mcp-server.json, the widget bundle, tool roles, the build-tree hash) are still read on demand when a server starts, not at import. (README: #server-side-i18n-fren)
19
+ - `mcp-readme-fr` prints every line number 1-based: a region opened by `lang:fr` without its English part was reported as "opened at line 0", and an unclosed region at the end of a file ending with a newline named the empty line after it as the last line.
20
+
10
21
  ## [0.7.0] - 2026-11-02
11
22
 
12
23
  ### Added
@@ -532,6 +543,7 @@ Initial published release.
532
543
  - MIT license, clonable as a starter template.
533
544
 
534
545
  [Unreleased]: https://github.com/elpiarthera/vantage-mcp-boilerplate/compare/v0.4.0...HEAD
546
+ [0.8.0]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.8.0
535
547
  [0.7.0]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.7.0
536
548
  [0.6.3]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.6.3
537
549
  [0.6.2]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.6.2
package/README.fr.md CHANGED
@@ -23,7 +23,10 @@ src/
23
23
  toolRecord.ts # fiche d'outil typée : autorité, verbe, table, mécanisme, pagination, coût
24
24
  pagination.ts # pagination par curseur appliquée par l'outil (défaut, max fini, invalid_cursor)
25
25
  signedCursor.ts # primitive de curseur signé HMAC (createCursorSigner), utilisée par tout curseur
26
- buildIdentity.ts # identité de build dérivée de l'arbre servi
26
+ buildIdentity.ts # identité de build : le commit git construit (buildId) + le hachage des fichiers construits (buildHash)
27
+ serverIdentity.ts # titre, description, site, icône, instructions : package.json ou env
28
+ serverCard.ts # la MCP Server Card publique
29
+ landingPage.ts # la page d'installation servie à un navigateur sur /mcp
27
30
  stdio.ts # serveStdio(factory) : les deux ères du protocole sur stdio
28
31
  http.ts # adaptateur Node http.Server autour de l'app Hono
29
32
  httpApp.ts # createMcpHandler(factory) sur /mcp + la barrière OAuth
@@ -561,7 +564,10 @@ ligne.
561
564
  le client envoie `X-MCP-Locale: fr|en` (défaut `en`) ; le sélecteur navigateur suit
562
565
  `Accept-Language`. Les fichiers de ressources (`src/locales/{en,fr}.json`) gardent la
563
566
  convention `<Namespace>.<category>.<key>` de `@vantageos/mosaic-i18n`, FR et EN en
564
- parité.
567
+ parité. Ils sont intégrés au module au build (`scripts/embed-static.mjs` écrit
568
+ `src/embedded.generated.ts`, avec la version et les champs d'identité du paquet) : rien n'est lu sur le disque
569
+ à l'import, et un consommateur empaqueté (le bundler Convex, esbuild) exécute le paquet sans dossier `locales/`
570
+ ni `package.json` à côté ; `--check` refuse une copie périmée.
565
571
 
566
572
  ## Un README français généré depuis un README bilingue
567
573
 
@@ -623,6 +629,40 @@ modifie jamais `CHANGELOG.md`, dont le bloc `## [Unreleased]` reste vide. `npm r
623
629
  (`changelog.d/README.md`, `docs/releasing.md`). Deux pull requests qui ajoutent deux fichiers n'entrent jamais
624
630
  en conflit, là où deux modifications du même bloc le faisaient.
625
631
 
632
+ ## Identité du serveur et découverte
633
+
634
+ Ce qu'un hôte ou un annuaire lit du serveur avant et pendant la connexion est dérivé, jamais tapé dans le
635
+ source (`src/serverIdentity.ts`, mcp-server-standard S-49 / S-50) :
636
+
637
+ | Servi comme | Variable d'environnement | Sinon, `package.json` |
638
+ |---|---|---|
639
+ | `serverInfo.title` | `MCP_SERVER_TITLE` | `displayName` |
640
+ | `serverInfo.description` (anglais) | `MCP_SERVER_DESCRIPTION` | `description` |
641
+ | `serverInfo.description` (français, `X-MCP-Locale: fr`) | `MCP_SERVER_DESCRIPTION_FR` | `description_fr` |
642
+ | `serverInfo.websiteUrl` | `MCP_SERVER_WEBSITE_URL` (https, ou http en bouclage) | `homepage` |
643
+
644
+ Un fork modifie ces champs de `package.json`, ou définit les variables (`.env.example`). Une valeur qu'aucune
645
+ des deux sources ne porte arrête le serveur au démarrage, en nommant la variable et le champ : le serveur ne
646
+ sert jamais de valeur fictive.
647
+
648
+ - **Instructions** (`initialize` / `server/discover`) : comment un agent utilise les outils, dans la langue de
649
+ l'appelant, depuis `Mcp.server.instructions` dans `src/locales/{en,fr}.json`. Réécrivez-les pour vos
650
+ outils : `tests/serverMetadata.test.ts` échoue quand un outil servi n'y est pas nommé ou que le texte dépasse
651
+ 2048 octets (le budget de champ de Claude Code).
652
+ - **Icône** : un vectoriel, `src/serverIcon.ts` (remplacez le SVG par votre logo), servi à `/icon.svg` et
653
+ nommé dans `serverInfo.icons` ; en stdio il voyage en URI `data:`.
654
+ - **Carte serveur** : la MCP Server Card (forme SEP-2127, plus `capabilities` et un résumé `tools` lu sur les
655
+ outils enregistrés), servie sans identifiant à `/mcp/server-card`, `/.well-known/mcp/server-card.json`,
656
+ `/.well-known/mcp-server-card`, `/mcp.json` et `/.well-known/mcp.json`. Son `name` est en DNS inversé, dérivé
657
+ du site (`io.github.<propriétaire>/<nom>` pour une URL GitHub).
658
+ - **Page d'installation** : un `GET /mcp` de navigateur (sans `Authorization`, avec un `Accept` sans
659
+ `text/event-stream`) reçoit une page bilingue qui nomme l'URL MCP et comment se connecter, sur les deux
660
+ paliers. Toute requête MCP est inchangée, y compris le 401 + `WWW-Authenticate` du palier OAuth.
661
+
662
+ La carte, la page et l'URL de l'icône nomment `PUBLIC_BASE_URL` + `/mcp` quand il est défini. Non défini
663
+ (développement), elles nomment l'origine à laquelle la requête était adressée et partent avec
664
+ `Cache-Control: no-store`. Définissez `PUBLIC_BASE_URL` sur tout déploiement public.
665
+
626
666
  ## Packaging & distribution
627
667
 
628
668
  Quatre manifestes sont **générés** depuis `scripts/plugin-manifest.source.json` et
@@ -729,9 +769,13 @@ curl -s -H 'Cache-Control: no-cache' https://registry.npmjs.org/@vantageos%2Fmcp
729
769
 
730
770
  Sommes égales : le registre sert ce que la source construit. Un serveur en marche
731
771
  prouve de la même façon ce qu'il sert : `GET /health` renvoie
732
- `{ status, version, buildId }` avant toute authentification. `buildId` est un hachage
733
- des fichiers dont le processus s'exécute ; il change quand ils changent, ce que
734
- `version` ne garantit pas.
772
+ `{ status, version, buildId, buildHash }` avant toute authentification. `buildId` est le
773
+ commit git à partir duquel l'arbre servi a été construit, dérivé par `npm run build`
774
+ (`scripts/write-build-info.mjs` écrit `dist/build-info.json`) : `BUILD_COMMIT`, sinon le commit qu'un
775
+ hébergeur injecte au build (`RAILWAY_GIT_COMMIT_SHA`, `VERCEL_GIT_COMMIT_SHA`, `RENDER_GIT_COMMIT`,
776
+ `GITHUB_SHA`, `SOURCE_VERSION`), sinon `git rev-parse HEAD` ; sans aucun, le build échoue en nommant
777
+ `BUILD_COMMIT`. `buildHash` est un hachage des fichiers dont le processus s'exécute ; il change aussi
778
+ quand un arbre modifié ou un `dist/` retouché est servi, ce que ni `version` ni le commit ne garantissent.
735
779
 
736
780
  ## Couche UI MCP Apps
737
781
 
package/README.md CHANGED
@@ -22,7 +22,10 @@ src/
22
22
  toolRecord.ts # typed tool record: authority, verb, table, mechanism, pagination, cost
23
23
  pagination.ts # cursor pagination applied by the tool (default, finite max, invalid_cursor)
24
24
  signedCursor.ts # HMAC-signed cursor primitive (createCursorSigner), used by every cursor
25
- buildIdentity.ts # build identity derived from the served tree
25
+ buildIdentity.ts # build identity: the git commit built (buildId) + the hash of the built files (buildHash)
26
+ serverIdentity.ts # title, description, website, icon, instructions: package.json or env
27
+ serverCard.ts # the public MCP Server Card
28
+ landingPage.ts # the setup page a browser gets at /mcp
26
29
  stdio.ts # serveStdio(factory): both protocol eras over stdio
27
30
  http.ts # Node http.Server adapter around the Hono app
28
31
  httpApp.ts # createMcpHandler(factory) on /mcp + the OAuth gate
@@ -531,7 +534,10 @@ row value.
531
534
  client sends `X-MCP-Locale: fr|en` (default `en`); the browser picker follows
532
535
  `Accept-Language`. Resource files (`src/locales/{en,fr}.json`) keep the
533
536
  `<Namespace>.<category>.<key>` convention of `@vantageos/mosaic-i18n`, FR and EN
534
- in lockstep.
537
+ in lockstep. They are embedded in the module at build (`scripts/embed-static.mjs` writes
538
+ `src/embedded.generated.ts`, with the package version and identity fields), so nothing is read from disk at
539
+ import and a bundled consumer (the Convex bundler, esbuild) runs the package with no `locales/` folder or
540
+ `package.json` beside it; `--check` refuses a stale copy.
535
541
 
536
542
  ## A French README generated from a bilingual one
537
543
 
@@ -589,6 +595,39 @@ refuses a malformed fragment or an entry written under `## [Unreleased]`. At rel
589
595
  (`changelog.d/README.md`, `docs/releasing.md`). Two pull requests adding two files never conflict, where two
590
596
  edits of the same block did.
591
597
 
598
+ ## Server identity and discovery
599
+
600
+ What a host or a directory reads about the server before and at connection is derived, never typed in
601
+ source (`src/serverIdentity.ts`, mcp-server-standard S-49 / S-50):
602
+
603
+ | Served as | Environment override | Otherwise, `package.json` |
604
+ |---|---|---|
605
+ | `serverInfo.title` | `MCP_SERVER_TITLE` | `displayName` |
606
+ | `serverInfo.description` (English) | `MCP_SERVER_DESCRIPTION` | `description` |
607
+ | `serverInfo.description` (French, `X-MCP-Locale: fr`) | `MCP_SERVER_DESCRIPTION_FR` | `description_fr` |
608
+ | `serverInfo.websiteUrl` | `MCP_SERVER_WEBSITE_URL` (https, or http on loopback) | `homepage` |
609
+
610
+ A fork edits those `package.json` fields, or sets the variables (`.env.example`). A value neither source
611
+ carries stops the server at start, naming the variable and the field: the server never serves a placeholder.
612
+
613
+ - **Instructions** (`initialize` / `server/discover`): how an agent uses the tools, in the caller's locale, from
614
+ `Mcp.server.instructions` in `src/locales/{en,fr}.json`. Rewrite them for your tools:
615
+ `tests/serverMetadata.test.ts` fails when a served tool is not named or the text passes 2048 bytes (Claude
616
+ Code's field budget).
617
+ - **Icon**: a vector, `src/serverIcon.ts` (replace the SVG with your mark), served at `/icon.svg` and named
618
+ in `serverInfo.icons`; on stdio it travels as a `data:` URI.
619
+ - **Server card**: the MCP Server Card (SEP-2127 shape, plus `capabilities` and a `tools` summary read from
620
+ the registered tools), served without credential at `/mcp/server-card`, `/.well-known/mcp/server-card.json`,
621
+ `/.well-known/mcp-server-card`, `/mcp.json` and `/.well-known/mcp.json`. Its `name` is reverse-DNS, derived
622
+ from the website (`io.github.<owner>/<name>` for a GitHub URL).
623
+ - **Setup page**: a browser `GET /mcp` (no `Authorization`, an `Accept` without `text/event-stream`) gets a
624
+ bilingual page naming the MCP URL and how to connect, on both tiers. Every MCP request is unchanged,
625
+ including the OAuth tier's 401 + `WWW-Authenticate`.
626
+
627
+ The card, the page and the icon URL name `PUBLIC_BASE_URL` + `/mcp` when it is set. Unset (development),
628
+ they name the origin the request was addressed to and are sent `Cache-Control: no-store`. Set
629
+ `PUBLIC_BASE_URL` on every public deployment.
630
+
592
631
  ## Packaging & distribution
593
632
 
594
633
  Four manifests are **generated** from `scripts/plugin-manifest.source.json` and
@@ -690,9 +729,13 @@ curl -s -H 'Cache-Control: no-cache' https://registry.npmjs.org/@vantageos%2Fmcp
690
729
  ```
691
730
 
692
731
  Equal sums: the registry serves what the source builds. A running server proves what
693
- it serves the same way: `GET /health` returns `{ status, version, buildId }` before any
694
- authentication. `buildId` is a hash of the files the process runs from, so it changes
695
- when they change, which `version` does not guarantee.
732
+ it serves the same way: `GET /health` returns `{ status, version, buildId, buildHash }` before any
733
+ authentication. `buildId` is the git commit the served tree was built from, derived by `npm run build`
734
+ (`scripts/write-build-info.mjs` writes `dist/build-info.json`): `BUILD_COMMIT`, else the commit a host
735
+ injects at build time (`RAILWAY_GIT_COMMIT_SHA`, `VERCEL_GIT_COMMIT_SHA`, `RENDER_GIT_COMMIT`, `GITHUB_SHA`,
736
+ `SOURCE_VERSION`), else `git rev-parse HEAD`; with none, the build fails, naming `BUILD_COMMIT`. `buildHash` is
737
+ a hash of the files the process runs from, so it also changes when a dirty tree or a patched `dist/` is
738
+ served, which neither `version` nor the commit guarantees.
696
739
 
697
740
  ## MCP Apps UI layer
698
741
 
@@ -0,0 +1 @@
1
+ {"commit":"4193f2a535d8229f071524265a7e6bb98e19d34e"}
@@ -17,3 +17,19 @@
17
17
  export declare function computeBuildIdentity(dir: string): string;
18
18
  /** The identity of the tree THIS process runs from, computed once. */
19
19
  export declare function getBuildIdentity(): string;
20
+ /**
21
+ * The git commit the served tree was built from: the `buildId` of /health and serverInfo (C-25,
22
+ * ruling: build identity = the commit). It is DERIVED at build time by
23
+ * scripts/write-build-info.mjs (`npm run build`), which writes `dist/build-info.json`, and read
24
+ * here — never typed. `src/` and `dist/` both sit one level under the package root, so the same
25
+ * relative URL resolves in source mode (vitest) and in the built output.
26
+ *
27
+ * The hash of the built files above stays served beside it as `buildHash`: the commit names the
28
+ * source, the hash proves the files a process runs from (a dirty tree or a patched dist changes
29
+ * the hash, not the commit).
30
+ */
31
+ export declare const BUILD_INFO_URL: URL;
32
+ /** The commit recorded in a build-info file. Throws, naming the file, if it is not readable. */
33
+ export declare function readBuildCommit(path: string): string;
34
+ /** The build commit of THIS process, read once. */
35
+ export declare function getBuildCommit(): string;
@@ -60,3 +60,42 @@ export function getBuildIdentity() {
60
60
  processIdentity ??= computeBuildIdentity(dirname(fileURLToPath(import.meta.url)));
61
61
  return processIdentity;
62
62
  }
63
+ /**
64
+ * The git commit the served tree was built from: the `buildId` of /health and serverInfo (C-25,
65
+ * ruling: build identity = the commit). It is DERIVED at build time by
66
+ * scripts/write-build-info.mjs (`npm run build`), which writes `dist/build-info.json`, and read
67
+ * here — never typed. `src/` and `dist/` both sit one level under the package root, so the same
68
+ * relative URL resolves in source mode (vitest) and in the built output.
69
+ *
70
+ * The hash of the built files above stays served beside it as `buildHash`: the commit names the
71
+ * source, the hash proves the files a process runs from (a dirty tree or a patched dist changes
72
+ * the hash, not the commit).
73
+ */
74
+ export const BUILD_INFO_URL = new URL("../dist/build-info.json", import.meta.url);
75
+ const FULL_COMMIT = /^[0-9a-f]{40}$/;
76
+ /** The commit recorded in a build-info file. Throws, naming the file, if it is not readable. */
77
+ export function readBuildCommit(path) {
78
+ let commit;
79
+ try {
80
+ commit = JSON.parse(readFileSync(path, "utf8")).commit;
81
+ }
82
+ catch (error) {
83
+ throw new Error(tBoth("buildIdentity.commitUnreadable", {
84
+ file: path,
85
+ reason: error instanceof Error ? error.message : String(error),
86
+ }));
87
+ }
88
+ if (typeof commit !== "string" || !FULL_COMMIT.test(commit)) {
89
+ throw new Error(tBoth("buildIdentity.commitUnreadable", {
90
+ file: path,
91
+ reason: "commit is not a 40-hex git commit id",
92
+ }));
93
+ }
94
+ return commit;
95
+ }
96
+ let processCommit;
97
+ /** The build commit of THIS process, read once. */
98
+ export function getBuildCommit() {
99
+ processCommit ??= readBuildCommit(fileURLToPath(BUILD_INFO_URL));
100
+ return processCommit;
101
+ }
@@ -0,0 +1,225 @@
1
+ export declare const EMBEDDED_LOCALES: {
2
+ readonly en: {
3
+ readonly Mcp: {
4
+ readonly auth: {
5
+ readonly unauthorized: "Unauthorized";
6
+ readonly originNotAllowed: "This origin is not allowed to call this endpoint";
7
+ };
8
+ readonly tenant: {
9
+ readonly permissionDenied: "permission denied: no tenant resolved for this caller";
10
+ };
11
+ readonly person: {
12
+ readonly identityNotConfigured: "permission denied: this tool needs the verified person, and this server resolves no person (identity is not configured)";
13
+ readonly noVerifiedSession: "permission denied: this tool needs the verified person, and this call carries no verified session";
14
+ readonly refused: "permission denied: the verified person behind this call could not be resolved";
15
+ };
16
+ readonly rateLimit: {
17
+ readonly exceeded: "too many tool calls: retry in {seconds} s";
18
+ };
19
+ readonly validation: {
20
+ readonly messageTooLong: "message exceeds the maximum length of 200 characters";
21
+ };
22
+ readonly oauth: {
23
+ readonly pickerTitle: "Connect {client}";
24
+ readonly pickerIntro: "{client} asks to act on your behalf. Choose the organisation it may reach, then approve or deny.";
25
+ readonly handoffTitle: "Returning to the application";
26
+ readonly handoffLink: "Continue to the application";
27
+ readonly pickerNoAllowedOrganisation: "None of your organisations is authorised on this server.";
28
+ readonly pickerDestination: "After approval you are sent to:";
29
+ readonly pickerScope: "Requested access:";
30
+ readonly pickerUnverified: "Unverified client: it registered itself automatically and nobody has vouched for it. Approve only if you recognise the address above.";
31
+ readonly pickerOrganisation: "Organisation";
32
+ readonly approve: "Approve";
33
+ readonly deny: "Deny";
34
+ readonly denied: "The request was denied; you can close this window.";
35
+ readonly unavailable: "Authorization is not configured on this server.";
36
+ readonly inMemoryWarning: "Running on in-memory OAuth stores: codes and registered clients are lost on restart and not shared between instances. Fine for development; set {knob}=1 to acknowledge, or inject durable adapters.";
37
+ readonly consentTokenRequired: "The consent token is missing: reload the page and try again.";
38
+ readonly registryFull: "Client registration is temporarily unavailable: the registry is full. Retry later.";
39
+ readonly devInstanceRemote: "Development Clerk instance on a remote server: its hosted sign-in page redirects only to localhost or to the instance Home URL. Set the development instance's Home URL (development origin) to this server's origin for a remote test and revert it afterwards, or use a Clerk production instance (see README, \"Sign-in with ChatGPT and Claude\").";
40
+ };
41
+ readonly manifest: {
42
+ readonly unreadableField: "cannot read field \"{field}\" of {source} (expected: {expected})";
43
+ readonly expect: {
44
+ readonly string: "a non-empty string";
45
+ readonly semver: "a semantic version (x.y.z)";
46
+ readonly object: "an object";
47
+ readonly boolean: "true or false";
48
+ readonly positiveInteger: "a positive whole number";
49
+ readonly atLeastPerTool: "at least the perTool ceiling";
50
+ readonly stringArray: "an array of non-empty strings";
51
+ };
52
+ };
53
+ readonly toolRoles: {
54
+ readonly identityMissing: "the identity stores are not injected (AppDeps.identity): on the OAuth tier a caller's role is decided for the organisation the identity layer resolves, so both are required";
55
+ readonly denied: "permission denied: your role does not grant the tool {tool} ({reason})";
56
+ readonly unresolved: "permission denied: your role could not be read, so no tool is granted";
57
+ readonly configMissing: "{variable} is not set: on the OAuth tier every tool is decided by the caller's role, read from that roles file";
58
+ readonly configInvalid: "{variable} ({path}) is not a valid roles file: {problem}";
59
+ };
60
+ readonly toolRecord: {
61
+ readonly unsound: "tool records or the context ceiling are unsound: {problems}";
62
+ };
63
+ readonly pagination: {
64
+ readonly invalidCursor: "invalid cursor: it was not issued by this tool or is no longer valid; start again without a cursor";
65
+ };
66
+ readonly lake: {
67
+ readonly unreadable: "the data source answered something this server cannot read; no row is shown rather than a partial or wrong page";
68
+ };
69
+ readonly buildIdentity: {
70
+ readonly unreadable: "cannot compute the build identity: {dir} is unreadable ({reason})";
71
+ readonly empty: "cannot compute the build identity: {dir} is empty (no file), nothing was built";
72
+ readonly commitUnreadable: "cannot read the build commit from {file} ({reason}): run npm run build, which writes it (scripts/write-build-info.mjs)";
73
+ };
74
+ readonly config: {
75
+ readonly refused: "configuration refused: {problems}";
76
+ readonly missing: "{env} is missing (expected: {expected})";
77
+ readonly invalid: "{env} is set but invalid (expected: {expected}; value not shown)";
78
+ readonly expect: {
79
+ readonly string: "a non-empty string";
80
+ readonly integer: "an integer from {min} to {max}";
81
+ readonly integerAtLeast: "an integer of at least {min}";
82
+ readonly boolean: "\"0\" or \"1\"";
83
+ readonly url: "an https URL (http on loopback only)";
84
+ readonly originList: "a comma-separated list of origins (scheme://host[:port], no path)";
85
+ readonly enum: "one of: {values}";
86
+ readonly json: "a JSON object or array";
87
+ };
88
+ };
89
+ readonly server: {
90
+ readonly instructions: "This server exposes three read-only example tools.\n1. Call whoami first. It returns who is calling on this connection (null on the public tier and on stdio), the caller's role, and grantedTools: the tools that role may call. Never ask the user for their identity and never pass one as an argument.\n2. ping proves the connection end to end; pass message to have it echoed back.\n3. list_items lists the items of the caller's organisation and renders them in an interactive widget where the client supports MCP Apps. It is paged: pass the returned nextCursor to read the next page; exhausted: true means the last page. Never invent a cursor.\n4. A result with isError and structuredContent.error.code is a refusal, not an empty result: rate_limited (wait retryAfterMs, then retry once), invalid_cursor (restart from the first page), lake_unreadable, TOOL_ROLE_DENIED or TOOL_ROLE_UNRESOLVED (the caller may not use this tool). Report it to the user; never read it as \"nothing exists\".\n5. Send the X-MCP-Locale header (en or fr) to receive messages in that language.";
91
+ readonly identityMissing: "the server identity has no {item}: set {variable}, or the \"{field}\" field of package.json";
92
+ readonly identityInvalid: "the server identity's {item} is not an https URL (http on a loopback host only): {source}";
93
+ };
94
+ readonly landing: {
95
+ readonly pageTitle: "{title} — MCP server";
96
+ readonly intro: "This address is a Model Context Protocol (MCP) server. Add it to your AI client as a remote MCP server:";
97
+ readonly claudeAi: "Claude.ai: add a custom connector with this URL, then Connect.";
98
+ readonly chatgpt: "ChatGPT: add a custom MCP server (developer mode) with this URL.";
99
+ readonly claudeCode: "Claude Code:";
100
+ readonly other: "Any other MCP client: a Streamable HTTP server at the URL above.";
101
+ readonly authOauth: "Your client then opens a sign-in page (OAuth 2.1). There is no token to copy.";
102
+ readonly authPublic: "This server runs without sign-in: connect directly.";
103
+ readonly website: "Website:";
104
+ };
105
+ readonly widget: {
106
+ readonly description: "Shows the example items returned by list_items as a list, with their name and status; read-only, it makes no request of its own.";
107
+ };
108
+ };
109
+ };
110
+ readonly fr: {
111
+ readonly Mcp: {
112
+ readonly auth: {
113
+ readonly unauthorized: "Non autorisé";
114
+ readonly originNotAllowed: "Cette origine n'est pas autorisée à appeler ce point d'accès";
115
+ };
116
+ readonly tenant: {
117
+ readonly permissionDenied: "permission refusée : aucun tenant résolu pour cet appelant";
118
+ };
119
+ readonly person: {
120
+ readonly identityNotConfigured: "permission refusée : cet outil exige la personne vérifiée, et ce serveur ne résout aucune personne (identité non configurée)";
121
+ readonly noVerifiedSession: "permission refusée : cet outil exige la personne vérifiée, et cet appel ne porte aucune session vérifiée";
122
+ readonly refused: "permission refusée : la personne vérifiée derrière cet appel n'a pas pu être résolue";
123
+ };
124
+ readonly rateLimit: {
125
+ readonly exceeded: "trop d’appels d’outils : réessayez dans {seconds} s";
126
+ };
127
+ readonly validation: {
128
+ readonly messageTooLong: "le message dépasse la longueur maximale de 200 caractères";
129
+ };
130
+ readonly oauth: {
131
+ readonly pickerTitle: "Connecter {client}";
132
+ readonly pickerIntro: "{client} demande à agir en votre nom. Choisissez l'organisation à laquelle il peut accéder, puis approuvez ou refusez.";
133
+ readonly handoffTitle: "Retour à l'application";
134
+ readonly handoffLink: "Continuer vers l'application";
135
+ readonly pickerNoAllowedOrganisation: "Aucune de vos organisations n'est autorisée sur ce serveur.";
136
+ readonly pickerDestination: "Redirection après validation vers :";
137
+ readonly pickerScope: "Accès demandé :";
138
+ readonly pickerUnverified: "Client non vérifié : il s'est enregistré automatiquement et personne ne s'en porte garant. N'approuvez que si vous reconnaissez l'adresse ci-dessus.";
139
+ readonly pickerOrganisation: "Organisation";
140
+ readonly approve: "Approuver";
141
+ readonly deny: "Refuser";
142
+ readonly denied: "La demande a été refusée ; vous pouvez fermer cette fenêtre.";
143
+ readonly unavailable: "L'autorisation n'est pas configurée sur ce serveur.";
144
+ readonly inMemoryWarning: "Stores OAuth en mémoire : les codes et les clients enregistrés sont perdus au redémarrage et non partagés entre instances. Acceptable en développement ; définissez {knob}=1 pour l'accepter, ou injectez des adaptateurs durables.";
145
+ readonly consentTokenRequired: "Le jeton de consentement est absent : rechargez la page et réessayez.";
146
+ readonly registryFull: "L'enregistrement de clients est temporairement indisponible : le registre est plein. Réessayez plus tard.";
147
+ readonly devInstanceRemote: "Instance Clerk de développement sur un serveur distant : sa page de connexion hébergée ne redirige que vers localhost ou vers l'URL d'accueil (Home URL) de l'instance. Pour un test distant, réglez l'URL d'accueil (origine de développement) de l'instance sur l'origine de ce serveur, puis rétablissez-la ; sinon utilisez une instance Clerk de production (voir README, « Connexion avec ChatGPT et Claude »).";
148
+ };
149
+ readonly manifest: {
150
+ readonly unreadableField: "champ \"{field}\" illisible dans {source} (attendu : {expected})";
151
+ readonly expect: {
152
+ readonly string: "une chaîne non vide";
153
+ readonly semver: "une version sémantique (x.y.z)";
154
+ readonly object: "un objet";
155
+ readonly boolean: "true ou false";
156
+ readonly positiveInteger: "un entier strictement positif";
157
+ readonly atLeastPerTool: "au moins le plafond perTool";
158
+ readonly stringArray: "un tableau de chaînes non vides";
159
+ };
160
+ };
161
+ readonly toolRoles: {
162
+ readonly identityMissing: "les stores d'identité ne sont pas injectés (AppDeps.identity) : sur le niveau OAuth, le rôle d'un appelant est décidé pour l'organisation que la couche d'identité résout, les deux sont donc requis";
163
+ readonly denied: "permission refusée : votre rôle n'accorde pas l'outil {tool} ({reason})";
164
+ readonly unresolved: "permission refusée : votre rôle n'a pas pu être lu, aucun outil n'est accordé";
165
+ readonly configMissing: "{variable} n'est pas défini : sur le niveau OAuth, chaque outil est décidé par le rôle de l'appelant, lu dans ce fichier de rôles";
166
+ readonly configInvalid: "{variable} ({path}) n'est pas un fichier de rôles valide : {problem}";
167
+ };
168
+ readonly toolRecord: {
169
+ readonly unsound: "fiches d'outils ou plafond de contexte invalides : {problems}";
170
+ };
171
+ readonly pagination: {
172
+ readonly invalidCursor: "curseur invalide : il n'a pas été émis par cet outil ou n'est plus valide ; recommencez sans curseur";
173
+ };
174
+ readonly lake: {
175
+ readonly unreadable: "la source de données a renvoyé une réponse illisible pour ce serveur ; aucune ligne n'est affichée plutôt qu'une page partielle ou fausse";
176
+ };
177
+ readonly buildIdentity: {
178
+ readonly unreadable: "identité de build incalculable : {dir} est illisible ({reason})";
179
+ readonly empty: "identité de build incalculable : {dir} est vide (aucun fichier), rien n'a été construit";
180
+ readonly commitUnreadable: "impossible de lire le commit de build dans {file} ({reason}) : lancez npm run build, qui l'écrit (scripts/write-build-info.mjs)";
181
+ };
182
+ readonly config: {
183
+ readonly refused: "configuration refusée : {problems}";
184
+ readonly missing: "{env} est manquante (attendu : {expected})";
185
+ readonly invalid: "{env} est définie mais invalide (attendu : {expected} ; valeur non affichée)";
186
+ readonly expect: {
187
+ readonly string: "une chaîne non vide";
188
+ readonly integer: "un entier de {min} à {max}";
189
+ readonly integerAtLeast: "un entier d'au moins {min}";
190
+ readonly boolean: "\"0\" ou \"1\"";
191
+ readonly url: "une URL https (http seulement sur la boucle locale)";
192
+ readonly originList: "une liste d'origines séparées par des virgules (schéma://hôte[:port], sans chemin)";
193
+ readonly enum: "une valeur parmi : {values}";
194
+ readonly json: "un objet ou un tableau JSON";
195
+ };
196
+ };
197
+ readonly server: {
198
+ readonly instructions: "Ce serveur expose trois outils d'exemple en lecture seule.\n1. Appelez whoami d'abord. Il renvoie qui appelle sur cette connexion (null sur le niveau public et en stdio), le rôle de l'appelant et grantedTools : les outils que ce rôle peut appeler. Ne demandez jamais son identité à l'utilisateur et n'en passez jamais une en argument.\n2. ping prouve la connexion de bout en bout ; passez message pour qu'il soit renvoyé tel quel.\n3. list_items liste les éléments de l'organisation de l'appelant et les affiche dans un widget interactif quand le client prend en charge MCP Apps. Il est paginé : passez le nextCursor renvoyé pour lire la page suivante ; exhausted: true signale la dernière page. N'inventez jamais de curseur.\n4. Un résultat avec isError et structuredContent.error.code est un refus, pas un résultat vide : rate_limited (attendez retryAfterMs, puis réessayez une fois), invalid_cursor (repartez de la première page), lake_unreadable, TOOL_ROLE_DENIED ou TOOL_ROLE_UNRESOLVED (l'appelant ne peut pas utiliser cet outil). Signalez-le à l'utilisateur ; ne le lisez jamais comme « rien n'existe ».\n5. Envoyez l'en-tête X-MCP-Locale (en ou fr) pour recevoir les messages dans cette langue.";
199
+ readonly identityMissing: "l'identité du serveur n'a pas de {item} : définissez {variable}, ou le champ « {field} » de package.json";
200
+ readonly identityInvalid: "{item} de l'identité du serveur n'est pas une URL https (http sur un hôte de bouclage seulement) : {source}";
201
+ };
202
+ readonly landing: {
203
+ readonly pageTitle: "{title} — serveur MCP";
204
+ readonly intro: "Cette adresse est un serveur Model Context Protocol (MCP). Ajoutez-la à votre client IA comme serveur MCP distant :";
205
+ readonly claudeAi: "Claude.ai : ajoutez un connecteur personnalisé avec cette URL, puis Connecter.";
206
+ readonly chatgpt: "ChatGPT : ajoutez un serveur MCP personnalisé (mode développeur) avec cette URL.";
207
+ readonly claudeCode: "Claude Code :";
208
+ readonly other: "Tout autre client MCP : un serveur Streamable HTTP à l'URL ci-dessus.";
209
+ readonly authOauth: "Votre client ouvre ensuite une page de connexion (OAuth 2.1). Aucun jeton à copier.";
210
+ readonly authPublic: "Ce serveur fonctionne sans connexion : branchez-vous directement.";
211
+ readonly website: "Site :";
212
+ };
213
+ readonly widget: {
214
+ readonly description: "Affiche sous forme de liste les éléments d'exemple renvoyés par list_items, avec leur nom et leur statut ; en lecture seule, il ne fait aucune requête lui-même.";
215
+ };
216
+ };
217
+ };
218
+ };
219
+ export declare const EMBEDDED_PACKAGE_VERSION = "0.8.0";
220
+ export declare const EMBEDDED_PACKAGE_IDENTITY: {
221
+ displayName: string;
222
+ description: string;
223
+ description_fr: string;
224
+ homepage: string;
225
+ };
@@ -0,0 +1,8 @@
1
+ // GENERATED by scripts/embed-static.mjs from src/locales/{en,fr}.json and package.json. Do not edit:
2
+ // `node scripts/embed-static.mjs --check` refuses any difference (tests/embedded-static.test.ts).
3
+ export const EMBEDDED_LOCALES = {
4
+ en: { "Mcp": { "auth": { "unauthorized": "Unauthorized", "originNotAllowed": "This origin is not allowed to call this endpoint" }, "tenant": { "permissionDenied": "permission denied: no tenant resolved for this caller" }, "person": { "identityNotConfigured": "permission denied: this tool needs the verified person, and this server resolves no person (identity is not configured)", "noVerifiedSession": "permission denied: this tool needs the verified person, and this call carries no verified session", "refused": "permission denied: the verified person behind this call could not be resolved" }, "rateLimit": { "exceeded": "too many tool calls: retry in {seconds} s" }, "validation": { "messageTooLong": "message exceeds the maximum length of 200 characters" }, "oauth": { "pickerTitle": "Connect {client}", "pickerIntro": "{client} asks to act on your behalf. Choose the organisation it may reach, then approve or deny.", "handoffTitle": "Returning to the application", "handoffLink": "Continue to the application", "pickerNoAllowedOrganisation": "None of your organisations is authorised on this server.", "pickerDestination": "After approval you are sent to:", "pickerScope": "Requested access:", "pickerUnverified": "Unverified client: it registered itself automatically and nobody has vouched for it. Approve only if you recognise the address above.", "pickerOrganisation": "Organisation", "approve": "Approve", "deny": "Deny", "denied": "The request was denied; you can close this window.", "unavailable": "Authorization is not configured on this server.", "inMemoryWarning": "Running on in-memory OAuth stores: codes and registered clients are lost on restart and not shared between instances. Fine for development; set {knob}=1 to acknowledge, or inject durable adapters.", "consentTokenRequired": "The consent token is missing: reload the page and try again.", "registryFull": "Client registration is temporarily unavailable: the registry is full. Retry later.", "devInstanceRemote": "Development Clerk instance on a remote server: its hosted sign-in page redirects only to localhost or to the instance Home URL. Set the development instance's Home URL (development origin) to this server's origin for a remote test and revert it afterwards, or use a Clerk production instance (see README, \"Sign-in with ChatGPT and Claude\")." }, "manifest": { "unreadableField": "cannot read field \"{field}\" of {source} (expected: {expected})", "expect": { "string": "a non-empty string", "semver": "a semantic version (x.y.z)", "object": "an object", "boolean": "true or false", "positiveInteger": "a positive whole number", "atLeastPerTool": "at least the perTool ceiling", "stringArray": "an array of non-empty strings" } }, "toolRoles": { "identityMissing": "the identity stores are not injected (AppDeps.identity): on the OAuth tier a caller's role is decided for the organisation the identity layer resolves, so both are required", "denied": "permission denied: your role does not grant the tool {tool} ({reason})", "unresolved": "permission denied: your role could not be read, so no tool is granted", "configMissing": "{variable} is not set: on the OAuth tier every tool is decided by the caller's role, read from that roles file", "configInvalid": "{variable} ({path}) is not a valid roles file: {problem}" }, "toolRecord": { "unsound": "tool records or the context ceiling are unsound: {problems}" }, "pagination": { "invalidCursor": "invalid cursor: it was not issued by this tool or is no longer valid; start again without a cursor" }, "lake": { "unreadable": "the data source answered something this server cannot read; no row is shown rather than a partial or wrong page" }, "buildIdentity": { "unreadable": "cannot compute the build identity: {dir} is unreadable ({reason})", "empty": "cannot compute the build identity: {dir} is empty (no file), nothing was built", "commitUnreadable": "cannot read the build commit from {file} ({reason}): run npm run build, which writes it (scripts/write-build-info.mjs)" }, "config": { "refused": "configuration refused: {problems}", "missing": "{env} is missing (expected: {expected})", "invalid": "{env} is set but invalid (expected: {expected}; value not shown)", "expect": { "string": "a non-empty string", "integer": "an integer from {min} to {max}", "integerAtLeast": "an integer of at least {min}", "boolean": "\"0\" or \"1\"", "url": "an https URL (http on loopback only)", "originList": "a comma-separated list of origins (scheme://host[:port], no path)", "enum": "one of: {values}", "json": "a JSON object or array" } }, "server": { "instructions": "This server exposes three read-only example tools.\n1. Call whoami first. It returns who is calling on this connection (null on the public tier and on stdio), the caller's role, and grantedTools: the tools that role may call. Never ask the user for their identity and never pass one as an argument.\n2. ping proves the connection end to end; pass message to have it echoed back.\n3. list_items lists the items of the caller's organisation and renders them in an interactive widget where the client supports MCP Apps. It is paged: pass the returned nextCursor to read the next page; exhausted: true means the last page. Never invent a cursor.\n4. A result with isError and structuredContent.error.code is a refusal, not an empty result: rate_limited (wait retryAfterMs, then retry once), invalid_cursor (restart from the first page), lake_unreadable, TOOL_ROLE_DENIED or TOOL_ROLE_UNRESOLVED (the caller may not use this tool). Report it to the user; never read it as \"nothing exists\".\n5. Send the X-MCP-Locale header (en or fr) to receive messages in that language.", "identityMissing": "the server identity has no {item}: set {variable}, or the \"{field}\" field of package.json", "identityInvalid": "the server identity's {item} is not an https URL (http on a loopback host only): {source}" }, "landing": { "pageTitle": "{title} — MCP server", "intro": "This address is a Model Context Protocol (MCP) server. Add it to your AI client as a remote MCP server:", "claudeAi": "Claude.ai: add a custom connector with this URL, then Connect.", "chatgpt": "ChatGPT: add a custom MCP server (developer mode) with this URL.", "claudeCode": "Claude Code:", "other": "Any other MCP client: a Streamable HTTP server at the URL above.", "authOauth": "Your client then opens a sign-in page (OAuth 2.1). There is no token to copy.", "authPublic": "This server runs without sign-in: connect directly.", "website": "Website:" }, "widget": { "description": "Shows the example items returned by list_items as a list, with their name and status; read-only, it makes no request of its own." } } },
5
+ fr: { "Mcp": { "auth": { "unauthorized": "Non autorisé", "originNotAllowed": "Cette origine n'est pas autorisée à appeler ce point d'accès" }, "tenant": { "permissionDenied": "permission refusée : aucun tenant résolu pour cet appelant" }, "person": { "identityNotConfigured": "permission refusée : cet outil exige la personne vérifiée, et ce serveur ne résout aucune personne (identité non configurée)", "noVerifiedSession": "permission refusée : cet outil exige la personne vérifiée, et cet appel ne porte aucune session vérifiée", "refused": "permission refusée : la personne vérifiée derrière cet appel n'a pas pu être résolue" }, "rateLimit": { "exceeded": "trop d’appels d’outils : réessayez dans {seconds} s" }, "validation": { "messageTooLong": "le message dépasse la longueur maximale de 200 caractères" }, "oauth": { "pickerTitle": "Connecter {client}", "pickerIntro": "{client} demande à agir en votre nom. Choisissez l'organisation à laquelle il peut accéder, puis approuvez ou refusez.", "handoffTitle": "Retour à l'application", "handoffLink": "Continuer vers l'application", "pickerNoAllowedOrganisation": "Aucune de vos organisations n'est autorisée sur ce serveur.", "pickerDestination": "Redirection après validation vers :", "pickerScope": "Accès demandé :", "pickerUnverified": "Client non vérifié : il s'est enregistré automatiquement et personne ne s'en porte garant. N'approuvez que si vous reconnaissez l'adresse ci-dessus.", "pickerOrganisation": "Organisation", "approve": "Approuver", "deny": "Refuser", "denied": "La demande a été refusée ; vous pouvez fermer cette fenêtre.", "unavailable": "L'autorisation n'est pas configurée sur ce serveur.", "inMemoryWarning": "Stores OAuth en mémoire : les codes et les clients enregistrés sont perdus au redémarrage et non partagés entre instances. Acceptable en développement ; définissez {knob}=1 pour l'accepter, ou injectez des adaptateurs durables.", "consentTokenRequired": "Le jeton de consentement est absent : rechargez la page et réessayez.", "registryFull": "L'enregistrement de clients est temporairement indisponible : le registre est plein. Réessayez plus tard.", "devInstanceRemote": "Instance Clerk de développement sur un serveur distant : sa page de connexion hébergée ne redirige que vers localhost ou vers l'URL d'accueil (Home URL) de l'instance. Pour un test distant, réglez l'URL d'accueil (origine de développement) de l'instance sur l'origine de ce serveur, puis rétablissez-la ; sinon utilisez une instance Clerk de production (voir README, « Connexion avec ChatGPT et Claude »)." }, "manifest": { "unreadableField": "champ \"{field}\" illisible dans {source} (attendu : {expected})", "expect": { "string": "une chaîne non vide", "semver": "une version sémantique (x.y.z)", "object": "un objet", "boolean": "true ou false", "positiveInteger": "un entier strictement positif", "atLeastPerTool": "au moins le plafond perTool", "stringArray": "un tableau de chaînes non vides" } }, "toolRoles": { "identityMissing": "les stores d'identité ne sont pas injectés (AppDeps.identity) : sur le niveau OAuth, le rôle d'un appelant est décidé pour l'organisation que la couche d'identité résout, les deux sont donc requis", "denied": "permission refusée : votre rôle n'accorde pas l'outil {tool} ({reason})", "unresolved": "permission refusée : votre rôle n'a pas pu être lu, aucun outil n'est accordé", "configMissing": "{variable} n'est pas défini : sur le niveau OAuth, chaque outil est décidé par le rôle de l'appelant, lu dans ce fichier de rôles", "configInvalid": "{variable} ({path}) n'est pas un fichier de rôles valide : {problem}" }, "toolRecord": { "unsound": "fiches d'outils ou plafond de contexte invalides : {problems}" }, "pagination": { "invalidCursor": "curseur invalide : il n'a pas été émis par cet outil ou n'est plus valide ; recommencez sans curseur" }, "lake": { "unreadable": "la source de données a renvoyé une réponse illisible pour ce serveur ; aucune ligne n'est affichée plutôt qu'une page partielle ou fausse" }, "buildIdentity": { "unreadable": "identité de build incalculable : {dir} est illisible ({reason})", "empty": "identité de build incalculable : {dir} est vide (aucun fichier), rien n'a été construit", "commitUnreadable": "impossible de lire le commit de build dans {file} ({reason}) : lancez npm run build, qui l'écrit (scripts/write-build-info.mjs)" }, "config": { "refused": "configuration refusée : {problems}", "missing": "{env} est manquante (attendu : {expected})", "invalid": "{env} est définie mais invalide (attendu : {expected} ; valeur non affichée)", "expect": { "string": "une chaîne non vide", "integer": "un entier de {min} à {max}", "integerAtLeast": "un entier d'au moins {min}", "boolean": "\"0\" ou \"1\"", "url": "une URL https (http seulement sur la boucle locale)", "originList": "une liste d'origines séparées par des virgules (schéma://hôte[:port], sans chemin)", "enum": "une valeur parmi : {values}", "json": "un objet ou un tableau JSON" } }, "server": { "instructions": "Ce serveur expose trois outils d'exemple en lecture seule.\n1. Appelez whoami d'abord. Il renvoie qui appelle sur cette connexion (null sur le niveau public et en stdio), le rôle de l'appelant et grantedTools : les outils que ce rôle peut appeler. Ne demandez jamais son identité à l'utilisateur et n'en passez jamais une en argument.\n2. ping prouve la connexion de bout en bout ; passez message pour qu'il soit renvoyé tel quel.\n3. list_items liste les éléments de l'organisation de l'appelant et les affiche dans un widget interactif quand le client prend en charge MCP Apps. Il est paginé : passez le nextCursor renvoyé pour lire la page suivante ; exhausted: true signale la dernière page. N'inventez jamais de curseur.\n4. Un résultat avec isError et structuredContent.error.code est un refus, pas un résultat vide : rate_limited (attendez retryAfterMs, puis réessayez une fois), invalid_cursor (repartez de la première page), lake_unreadable, TOOL_ROLE_DENIED ou TOOL_ROLE_UNRESOLVED (l'appelant ne peut pas utiliser cet outil). Signalez-le à l'utilisateur ; ne le lisez jamais comme « rien n'existe ».\n5. Envoyez l'en-tête X-MCP-Locale (en ou fr) pour recevoir les messages dans cette langue.", "identityMissing": "l'identité du serveur n'a pas de {item} : définissez {variable}, ou le champ « {field} » de package.json", "identityInvalid": "{item} de l'identité du serveur n'est pas une URL https (http sur un hôte de bouclage seulement) : {source}" }, "landing": { "pageTitle": "{title} — serveur MCP", "intro": "Cette adresse est un serveur Model Context Protocol (MCP). Ajoutez-la à votre client IA comme serveur MCP distant :", "claudeAi": "Claude.ai : ajoutez un connecteur personnalisé avec cette URL, puis Connecter.", "chatgpt": "ChatGPT : ajoutez un serveur MCP personnalisé (mode développeur) avec cette URL.", "claudeCode": "Claude Code :", "other": "Tout autre client MCP : un serveur Streamable HTTP à l'URL ci-dessus.", "authOauth": "Votre client ouvre ensuite une page de connexion (OAuth 2.1). Aucun jeton à copier.", "authPublic": "Ce serveur fonctionne sans connexion : branchez-vous directement.", "website": "Site :" }, "widget": { "description": "Affiche sous forme de liste les éléments d'exemple renvoyés par list_items, avec leur nom et leur statut ; en lecture seule, il ne fait aucune requête lui-même." } } },
6
+ };
7
+ export const EMBEDDED_PACKAGE_VERSION = "0.8.0";
8
+ export const EMBEDDED_PACKAGE_IDENTITY = { "displayName": "MCP Boilerplate", "description": "Clonable MCP server skeleton on the official @modelcontextprotocol/server SDK v2 (protocol 2026-07-28 + 2025 legacy, stdio + stateless Streamable HTTP), with its own OAuth 2.1 authorization server (Clerk person + organisation, cloud-identity), MCP Apps UI, tenant scoping, FR/EN i18n and a declarative eval harness.", "description_fr": "Squelette de serveur MCP clonable sur le SDK officiel @modelcontextprotocol/server v2 (protocole 2026-07-28 + héritage 2025, stdio + Streamable HTTP sans état), avec son propre serveur d'autorisation OAuth 2.1 (personne + organisation Clerk, cloud-identity), UI MCP Apps, scoping tenant, i18n FR/EN et un harnais d'évaluations déclaratif.", "homepage": "https://github.com/elpiarthera/vantage-mcp-boilerplate#readme" };
package/dist/http.js CHANGED
@@ -15,11 +15,13 @@ const { allowedOrigins } = loadHttpSettings();
15
15
  // settings from the environment per request — a missing value answers 503, see
16
16
  // src/oauth/settings.ts.
17
17
  const externalIssuer = Boolean(process.env.MCP_OAUTH_ISSUER && process.env.MCP_OAUTH_AUDIENCE);
18
+ // S-50: the server card, the setup page and the icon name the public origin when it is set.
19
+ const publicBaseUrl = process.env.PUBLIC_BASE_URL || undefined;
18
20
  const app = buildHttpApp(authMode === "oauth"
19
21
  ? externalIssuer
20
- ? { authMode, auth: loadAuthConfig(), uiDomain, allowedOrigins }
21
- : { authMode, authorizationServer: {}, uiDomain, allowedOrigins }
22
- : { authMode, uiDomain, allowedOrigins });
22
+ ? { authMode, auth: loadAuthConfig(), uiDomain, allowedOrigins, publicBaseUrl }
23
+ : { authMode, authorizationServer: {}, uiDomain, allowedOrigins, publicBaseUrl }
24
+ : { authMode, uiDomain, allowedOrigins, publicBaseUrl });
23
25
  const port = Number(process.env.PORT ?? 8787);
24
26
  // Hono's fetch handler adapted onto Node's http server, with the request-body cap
25
27
  // (MCP_MAX_BODY_BYTES, default 1 MiB) enforced while the body streams in: src/nodeAdapter.ts.
package/dist/httpApp.d.ts CHANGED
@@ -41,6 +41,12 @@ export interface AppDeps {
41
41
  auth?: AuthConfig;
42
42
  /** Own authorization server (R1). Takes precedence over `auth`. */
43
43
  authorizationServer?: AuthorizationServerDeps;
44
+ /**
45
+ * The public base URL (`PUBLIC_BASE_URL`, src/http.ts): the server card, the setup page and the
46
+ * serverInfo icon name `<base>/mcp` and `<base>/icon.svg`. Absent (development), they name the
47
+ * origin the request was addressed to, and those answers are sent `no-store`.
48
+ */
49
+ publicBaseUrl?: string;
44
50
  /** Dedicated sandbox origin for the MCP Apps widget (`_meta.ui.domain`). */
45
51
  uiDomain?: string;
46
52
  /**