@vantageos/mcp-boilerplate 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +27 -3
- package/README.fr.md +18 -0
- package/README.md +18 -0
- package/mcp-server.json +1 -1
- package/package.json +5 -3
- package/plugin.json +1 -1
- package/scripts/readme-fr.mjs +155 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vantage-mcp-boilerplate",
|
|
3
3
|
"displayName": "VantageOS MCP Boilerplate",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.1",
|
|
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,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.1] - 2026-11-01
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `mcp-readme-fr` run as `npx mcp-readme-fr` (through the `node_modules/.bin` symlink) did nothing in 0.6.0: the entry guard compared the link path with the real file, so `--write` wrote nothing and `--check` exited 0 without checking. The guard now compares real paths (same fix in `scripts/sync-plugin-version.mjs`), and two tests run the bin through a symlink. Use 0.6.1; 0.6.0 is deprecated.
|
|
14
|
+
|
|
15
|
+
## [0.6.0] - 2026-11-01
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `mcp-readme-fr` (bin, `scripts/readme-fr.mjs`): generates `README.fr.md` from a bilingual `README.md` whose passages carry explicit `<!-- lang:en -->` / `<!-- lang:fr -->` / `<!-- /lang -->` markers (mcp-standard C-1: one French file, never hand-copied). `--check` fails when the two diverge, a malformed marker is an error naming its line, and a hand-written `README.fr.md` (no banner) is never overwritten. `tests/readmeFr.test.ts`.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- The CHANGELOG link block covers every version: a GitHub compare or tag link where a tag exists (v0.1.1, v0.1.2, v0.3.0, v0.4.0), the npm page of the version otherwise (0.1.0, 0.2.x, 0.5.0; the dead `v0.1.0` tag link is replaced). `[Unreleased]` compares the newest tag, v0.4.0. `tests/changelogLinks.test.ts` fails when a `## [x]` heading has no link line, a link has no heading, or a URL is of no known form.
|
|
22
|
+
|
|
10
23
|
## [0.5.0] - 2026-10-29
|
|
11
24
|
|
|
12
25
|
> **Breaking for multi-instance deployments.** Cursors are now signed under a key drawn once per
|
|
@@ -479,6 +492,17 @@ Initial published release.
|
|
|
479
492
|
- Reproducible build via `npm ci`; 52/52 tests passing at release time.
|
|
480
493
|
- MIT license, clonable as a starter template.
|
|
481
494
|
|
|
482
|
-
[Unreleased]: https://github.com/elpiarthera/vantage-mcp-boilerplate/compare/v0.
|
|
483
|
-
[0.
|
|
484
|
-
[0.
|
|
495
|
+
[Unreleased]: https://github.com/elpiarthera/vantage-mcp-boilerplate/compare/v0.4.0...HEAD
|
|
496
|
+
[0.6.1]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.6.1
|
|
497
|
+
[0.6.0]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.6.0
|
|
498
|
+
[0.5.0]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.5.0
|
|
499
|
+
[0.4.0]: https://github.com/elpiarthera/vantage-mcp-boilerplate/compare/v0.3.0...v0.4.0
|
|
500
|
+
[0.3.0]: https://github.com/elpiarthera/vantage-mcp-boilerplate/compare/v0.1.2...v0.3.0
|
|
501
|
+
[0.2.4]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.2.4
|
|
502
|
+
[0.2.3]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.2.3
|
|
503
|
+
[0.2.2]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.2.2
|
|
504
|
+
[0.2.1]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.2.1
|
|
505
|
+
[0.2.0]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.2.0
|
|
506
|
+
[0.1.2]: https://github.com/elpiarthera/vantage-mcp-boilerplate/compare/v0.1.1...v0.1.2
|
|
507
|
+
[0.1.1]: https://github.com/elpiarthera/vantage-mcp-boilerplate/releases/tag/v0.1.1
|
|
508
|
+
[0.1.0]: https://www.npmjs.com/package/@vantageos/mcp-boilerplate/v/0.1.0
|
package/README.fr.md
CHANGED
|
@@ -517,6 +517,24 @@ le client envoie `X-MCP-Locale: fr|en` (défaut `en`) ; le sélecteur navigateur
|
|
|
517
517
|
convention `<Namespace>.<category>.<key>` de `@vantageos/mosaic-i18n`, FR et EN en
|
|
518
518
|
parité.
|
|
519
519
|
|
|
520
|
+
## Un README français généré depuis un README bilingue
|
|
521
|
+
|
|
522
|
+
Un serveur dont le `README.md` porte l'anglais et le français côte à côte livre un `README.fr.md` généré depuis lui
|
|
523
|
+
(mcp-standard C-1), jamais recopié à la main. Marquez chaque passage bilingue, puis lancez le générateur :
|
|
524
|
+
|
|
525
|
+
```markdown
|
|
526
|
+
<!-- lang:en -->
|
|
527
|
+
A demo server.
|
|
528
|
+
<!-- lang:fr -->
|
|
529
|
+
Un serveur de démonstration.
|
|
530
|
+
<!-- /lang -->
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
Le texte hors d'une région marquée (code, tableaux, liens) est copié dans les deux. `npx mcp-readme-fr --write` écrit
|
|
534
|
+
`README.fr.md` ; `npx mcp-readme-fr --check` (à mettre dans `npm test`) échoue quand les deux divergent. Un marqueur
|
|
535
|
+
mal formé est une erreur qui nomme sa ligne, et le générateur n'écrase jamais un `README.fr.md` écrit à la main (sans
|
|
536
|
+
sa bannière). Ce dépôt garde sa propre traduction écrite à la main.
|
|
537
|
+
|
|
520
538
|
## Tests
|
|
521
539
|
|
|
522
540
|
```bash
|
package/README.md
CHANGED
|
@@ -489,6 +489,24 @@ client sends `X-MCP-Locale: fr|en` (default `en`); the browser picker follows
|
|
|
489
489
|
`<Namespace>.<category>.<key>` convention of `@vantageos/mosaic-i18n`, FR and EN
|
|
490
490
|
in lockstep.
|
|
491
491
|
|
|
492
|
+
## A French README generated from a bilingual one
|
|
493
|
+
|
|
494
|
+
A server whose `README.md` carries English and French side by side ships a `README.fr.md` generated from it
|
|
495
|
+
(mcp-standard C-1), never hand-copied. Mark each bilingual passage, then run the generator:
|
|
496
|
+
|
|
497
|
+
```markdown
|
|
498
|
+
<!-- lang:en -->
|
|
499
|
+
A demo server.
|
|
500
|
+
<!-- lang:fr -->
|
|
501
|
+
Un serveur de démonstration.
|
|
502
|
+
<!-- /lang -->
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Text outside a marked region (code, tables, links) is copied to both. `npx mcp-readme-fr --write` writes
|
|
506
|
+
`README.fr.md`; `npx mcp-readme-fr --check` (put it in `npm test`) fails when the two diverge. A malformed marker is
|
|
507
|
+
an error naming its line, and the generator never overwrites a hand-written `README.fr.md` (one without its banner).
|
|
508
|
+
This repository keeps its own hand translation.
|
|
509
|
+
|
|
492
510
|
## Testing
|
|
493
511
|
|
|
494
512
|
```bash
|
package/mcp-server.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vantageos/mcp-boilerplate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"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.",
|
|
5
5
|
"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.",
|
|
6
6
|
"type": "module",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"./package.json": "./package.json"
|
|
19
19
|
},
|
|
20
20
|
"bin": {
|
|
21
|
-
"mcp-boilerplate": "./dist/stdio.js"
|
|
21
|
+
"mcp-boilerplate": "./dist/stdio.js",
|
|
22
|
+
"mcp-readme-fr": "./scripts/readme-fr.mjs"
|
|
22
23
|
},
|
|
23
24
|
"files": [
|
|
24
25
|
"dist",
|
|
@@ -36,7 +37,8 @@
|
|
|
36
37
|
"scripts/generate-signing-key.mjs",
|
|
37
38
|
"docs",
|
|
38
39
|
"examples/claude-desktop.json",
|
|
39
|
-
"examples/oauth.env.example"
|
|
40
|
+
"examples/oauth.env.example",
|
|
41
|
+
"scripts/readme-fr.mjs"
|
|
40
42
|
],
|
|
41
43
|
"publishConfig": {
|
|
42
44
|
"access": "public"
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "vantage-mcp-boilerplate",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.1",
|
|
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"
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* readme-fr — generate README.fr.md from a bilingual README.md (mcp-standard C-1: a French reader gets one French
|
|
4
|
+
* file). The French text is written once, in README.md, next to the English; README.fr.md is derived, never
|
|
5
|
+
* hand-copied, and `--check` fails when the two diverge.
|
|
6
|
+
*
|
|
7
|
+
* The language of a passage is read from an explicit marker, never guessed from its words. Census of the forms:
|
|
8
|
+
* <!-- lang:en --> opens a bilingual region; the lines that follow are its English part (dropped)
|
|
9
|
+
* <!-- lang:fr --> switches the open region to its French part (kept)
|
|
10
|
+
* <!-- /lang --> closes the region
|
|
11
|
+
* A marker stands alone on its line. Text outside a region is shared and copied as is (code blocks, tables, links).
|
|
12
|
+
* A fenced code block (``` or ~~~) is copied verbatim with the part it sits in; a marker inside it is text. An
|
|
13
|
+
* unclosed fence is an error naming its opening line.
|
|
14
|
+
* Errors, each naming its line (exit 2): an unknown language, a nested region, a French part before the English one,
|
|
15
|
+
* a region closed with no French part, a close with no open region, a region left open at the end of the file.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* mcp-readme-fr --check [--dir <path>] regenerate in memory; exit 1 naming the first differing line
|
|
19
|
+
* mcp-readme-fr --write [--dir <path>] write README.fr.md; refuses (exit 2) to overwrite a README.fr.md that
|
|
20
|
+
* does not start with the generated-file banner (a hand translation)
|
|
21
|
+
* No dependencies: node built-ins only.
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { join, resolve } from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
|
|
27
|
+
export const BANNER =
|
|
28
|
+
"<!-- Généré depuis README.md par mcp-readme-fr (@vantageos/mcp-boilerplate) : ne pas modifier à la main. -->";
|
|
29
|
+
|
|
30
|
+
const MARKER = /^\s*<!--\s*(lang:([A-Za-z-]+)|\/lang)\s*-->\s*$/;
|
|
31
|
+
const FENCE = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
32
|
+
|
|
33
|
+
/** README.md text -> { fr, errors }. `fr` is the README.fr.md content (banner first). */
|
|
34
|
+
export function generate(text) {
|
|
35
|
+
const out = [BANNER, ""];
|
|
36
|
+
const errors = [];
|
|
37
|
+
let state = "shared"; // shared | en | fr
|
|
38
|
+
let openedAt = 0;
|
|
39
|
+
const lines = text.split("\n");
|
|
40
|
+
let fence = null; // { char, len, at } while inside a fenced code block
|
|
41
|
+
lines.forEach((line, i) => {
|
|
42
|
+
const n = i + 1;
|
|
43
|
+
// A fenced code block (``` or ~~~, CommonMark: up to 3 spaces of indent) is copied verbatim with the part it
|
|
44
|
+
// sits in: a marker inside it is text, never a region.
|
|
45
|
+
const f = FENCE.exec(line);
|
|
46
|
+
if (fence !== null) {
|
|
47
|
+
if (state !== "en") out.push(line);
|
|
48
|
+
if (f && f[1][0] === fence.char && f[1].length >= fence.len && f[2].trim() === "")
|
|
49
|
+
fence = null;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (f) {
|
|
53
|
+
fence = { char: f[1][0], len: f[1].length, at: n };
|
|
54
|
+
if (state !== "en") out.push(line);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const m = MARKER.exec(line);
|
|
58
|
+
if (!m) {
|
|
59
|
+
if (state !== "en") out.push(line);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (m[1] === "/lang") {
|
|
63
|
+
if (state === "shared") errors.push(`line ${n}: <!-- /lang --> closes no open region`);
|
|
64
|
+
else if (state === "en")
|
|
65
|
+
errors.push(
|
|
66
|
+
`line ${n}: the region opened at line ${openedAt} has no French part (<!-- lang:fr -->)`
|
|
67
|
+
);
|
|
68
|
+
state = "shared";
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const lang = m[2].toLowerCase();
|
|
72
|
+
if (lang !== "en" && lang !== "fr") {
|
|
73
|
+
errors.push(`line ${n}: unknown language marker lang:${m[2]} (known: en, fr)`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (lang === "en") {
|
|
77
|
+
if (state !== "shared")
|
|
78
|
+
errors.push(
|
|
79
|
+
`line ${n}: nested region (the region opened at line ${openedAt} is still open)`
|
|
80
|
+
);
|
|
81
|
+
state = "en";
|
|
82
|
+
openedAt = n;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// lang === "fr"
|
|
86
|
+
if (state === "shared")
|
|
87
|
+
errors.push(
|
|
88
|
+
`line ${n}: lang:fr opens a region before its English part (<!-- lang:en --> first)`
|
|
89
|
+
);
|
|
90
|
+
else if (state === "fr")
|
|
91
|
+
errors.push(`line ${n}: a second lang:fr in the region opened at line ${openedAt}`);
|
|
92
|
+
state = "fr";
|
|
93
|
+
});
|
|
94
|
+
if (fence !== null)
|
|
95
|
+
errors.push(`line ${fence.at}: unclosed code fence (${fence.char.repeat(fence.len)})`);
|
|
96
|
+
if (state !== "shared")
|
|
97
|
+
errors.push(
|
|
98
|
+
`line ${lines.length}: the region opened at line ${openedAt} is unclosed (<!-- /lang -->)`
|
|
99
|
+
);
|
|
100
|
+
return { fr: out.join("\n"), errors };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function main(argv, cwd = process.cwd()) {
|
|
104
|
+
const mode = argv.includes("--write") ? "write" : argv.includes("--check") ? "check" : null;
|
|
105
|
+
const dirAt = argv.indexOf("--dir");
|
|
106
|
+
const dir = resolve(cwd, dirAt >= 0 ? (argv[dirAt + 1] ?? ".") : ".");
|
|
107
|
+
if (mode === null) {
|
|
108
|
+
console.error("mcp-readme-fr: --check or --write is required");
|
|
109
|
+
return 2;
|
|
110
|
+
}
|
|
111
|
+
const readmePath = join(dir, "README.md");
|
|
112
|
+
const frPath = join(dir, "README.fr.md");
|
|
113
|
+
if (!existsSync(readmePath)) {
|
|
114
|
+
console.error(`mcp-readme-fr: ${readmePath} not found`);
|
|
115
|
+
return 2;
|
|
116
|
+
}
|
|
117
|
+
const { fr, errors } = generate(readFileSync(readmePath, "utf8"));
|
|
118
|
+
if (errors.length > 0) {
|
|
119
|
+
for (const e of errors) console.error(`README.md ${e}`);
|
|
120
|
+
return 2;
|
|
121
|
+
}
|
|
122
|
+
const current = existsSync(frPath) ? readFileSync(frPath, "utf8") : null;
|
|
123
|
+
if (mode === "write") {
|
|
124
|
+
if (current !== null && !current.startsWith(BANNER)) {
|
|
125
|
+
console.error(
|
|
126
|
+
`mcp-readme-fr: ${frPath} was not generated (no banner): refusing to overwrite a hand translation`
|
|
127
|
+
);
|
|
128
|
+
return 2;
|
|
129
|
+
}
|
|
130
|
+
writeFileSync(frPath, fr);
|
|
131
|
+
console.log(`mcp-readme-fr: wrote ${frPath}`);
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
if (current === fr) {
|
|
135
|
+
console.log("mcp-readme-fr --check: README.fr.md matches README.md");
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
const a = (current ?? "").split("\n");
|
|
139
|
+
const b = fr.split("\n");
|
|
140
|
+
let i = 0;
|
|
141
|
+
while (i < Math.max(a.length, b.length) && a[i] === b[i]) i++;
|
|
142
|
+
console.error(
|
|
143
|
+
`mcp-readme-fr --check: README.fr.md differs from what README.md generates at line ${i + 1} — run mcp-readme-fr --write`
|
|
144
|
+
);
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// npm runs a bin through a node_modules/.bin SYMLINK: argv[1] is the link, import.meta.url the real file. Compare
|
|
149
|
+
// real paths, or main() never runs under npx (0.6.0: --check exited 0 having checked nothing).
|
|
150
|
+
if (
|
|
151
|
+
process.argv[1] &&
|
|
152
|
+
realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))
|
|
153
|
+
) {
|
|
154
|
+
process.exit(main(process.argv.slice(2)));
|
|
155
|
+
}
|