@vantageos/mcp-boilerplate 0.6.3 → 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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +39 -0
- package/README.fr.md +146 -10
- package/README.md +141 -12
- package/dist/build-info.json +1 -0
- package/dist/buildIdentity.d.ts +16 -0
- package/dist/buildIdentity.js +39 -0
- package/dist/embedded.generated.d.ts +225 -0
- package/dist/embedded.generated.js +8 -0
- package/dist/hostVerification.d.ts +15 -0
- package/dist/hostVerification.js +32 -0
- package/dist/http.js +9 -4
- package/dist/httpApp.d.ts +15 -0
- package/dist/httpApp.js +119 -9
- package/dist/i18n.js +6 -16
- package/dist/landingPage.d.ts +24 -0
- package/dist/landingPage.js +109 -0
- package/dist/listCursor.d.ts +10 -0
- package/dist/listCursor.js +37 -0
- package/dist/mcpServerFactory.d.ts +22 -2
- package/dist/mcpServerFactory.js +125 -22
- package/dist/oauth/authorizationServer.js +33 -12
- package/dist/oauth/picker.js +1 -0
- package/dist/oauth/settings.d.ts +2 -0
- package/dist/oauth/settings.js +2 -0
- package/dist/oauth/tokenIssuer.d.ts +0 -1
- package/dist/oauth/tokenIssuer.js +1 -18
- package/dist/pagination.d.ts +2 -1
- package/dist/pagination.js +2 -1
- package/dist/rateLimit.d.ts +33 -0
- package/dist/rateLimit.js +48 -0
- package/dist/servedToolRecords.d.ts +6 -0
- package/dist/servedToolRecords.js +11 -1
- package/dist/serverCard.d.ts +68 -0
- package/dist/serverCard.js +68 -0
- package/dist/serverIcon.d.ts +17 -0
- package/dist/serverIcon.js +26 -0
- package/dist/serverIdentity.d.ts +83 -0
- package/dist/serverIdentity.js +130 -0
- package/dist/serverManifest.d.ts +9 -1
- package/dist/serverManifest.js +10 -1
- package/dist/signedCursor.js +6 -1
- package/dist/stdio.js +2 -2
- package/dist/tenancy.d.ts +4 -14
- package/dist/tenancy.js +7 -18
- package/dist/toolRecord.d.ts +35 -2
- package/dist/toolRecord.js +44 -2
- package/dist/toolRoleGate.d.ts +41 -0
- package/dist/toolRoleGate.js +94 -0
- package/dist/toolRoles.d.ts +57 -0
- package/dist/toolRoles.js +113 -0
- package/dist/ui/sandboxDomain.d.ts +13 -0
- package/dist/ui/sandboxDomain.js +23 -0
- package/dist/ui/uiResource.d.ts +14 -1
- package/dist/ui/uiResource.js +11 -1
- package/dist/upstream/paginatedClient.js +1 -0
- package/dist/verifiedPerson.d.ts +6 -0
- package/dist/verifiedPerson.js +9 -0
- package/dist/version.js +6 -5
- package/docs/manufact-gap.md +200 -0
- package/docs/releasing.md +19 -0
- package/mcp-server.json +3 -2
- package/package.json +11 -3
- package/plugin.json +2 -4
- package/scripts/plugin-manifest.source.json +19 -0
- package/scripts/readme-fr.mjs +11 -4
- package/dist/locales/en.json +0 -77
- package/dist/locales/fr.json +0 -77
- package/dist/oauth/clientAssertion.d.ts +0 -28
- package/dist/oauth/clientAssertion.js +0 -106
|
@@ -133,6 +133,7 @@ function canonicalJson(value) {
|
|
|
133
133
|
}
|
|
134
134
|
return JSON.stringify(value) ?? "null";
|
|
135
135
|
}
|
|
136
|
+
// allow-local-identity: cache key of an upstream request (path + params); no credential, caller or tenant
|
|
136
137
|
function fingerprint(path, params) {
|
|
137
138
|
return createHash("sha256")
|
|
138
139
|
.update(canonicalJson({ path, params }))
|
package/dist/verifiedPerson.d.ts
CHANGED
|
@@ -77,6 +77,12 @@ export interface PersonContext {
|
|
|
77
77
|
export type PersonRefusalResult = CallToolResult & {
|
|
78
78
|
isError: true;
|
|
79
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* The identity layer's refusal for a caller it did not resolve (no resolution in the context = no_verified_session),
|
|
82
|
+
* or undefined when the caller is resolved. The agent-roles gate answers with it before deciding a role, so an
|
|
83
|
+
* unresolved caller gets the precise, stable identity code rather than a generic role denial — a refusal either way.
|
|
84
|
+
*/
|
|
85
|
+
export declare function callerRefusal(caller: CallerResolution | undefined, locale: McpLocale): PersonRefusalResult | undefined;
|
|
80
86
|
/**
|
|
81
87
|
* Declares that a tool needs the verified person: the handler runs only with one, and receives it
|
|
82
88
|
* as its second argument. No caller resolution in the context (stdio) is `no_verified_session`.
|
package/dist/verifiedPerson.js
CHANGED
|
@@ -98,6 +98,15 @@ function personRefusal(caller, locale) {
|
|
|
98
98
|
isError: true,
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* The identity layer's refusal for a caller it did not resolve (no resolution in the context = no_verified_session),
|
|
103
|
+
* or undefined when the caller is resolved. The agent-roles gate answers with it before deciding a role, so an
|
|
104
|
+
* unresolved caller gets the precise, stable identity code rather than a generic role denial — a refusal either way.
|
|
105
|
+
*/
|
|
106
|
+
export function callerRefusal(caller, locale) {
|
|
107
|
+
const resolution = caller ?? unresolved("no_verified_session");
|
|
108
|
+
return resolution.ok ? undefined : personRefusal(resolution, locale);
|
|
109
|
+
}
|
|
101
110
|
/**
|
|
102
111
|
* Declares that a tool needs the verified person: the handler runs only with one, and receives it
|
|
103
112
|
* as its second argument. No caller resolution in the context (stdio) is `no_verified_session`.
|
package/dist/version.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The advertised server version is DERIVED from package.json (never a literal).
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The advertised server version is DERIVED from package.json (never a literal): scripts/embed-static.mjs writes
|
|
3
|
+
* it into src/embedded.generated.ts at build, and `--check` (tests/embedded-static.test.ts) refuses a stale copy.
|
|
4
|
+
* It is a module value, not a file read at import: a bundled consumer carries no package.json beside this module
|
|
5
|
+
* (task k17aj33ks3ndyxgt7jch68rpwx8hz9x5).
|
|
5
6
|
*/
|
|
6
|
-
import {
|
|
7
|
+
import { EMBEDDED_PACKAGE_VERSION } from "./embedded.generated.js";
|
|
7
8
|
export const SERVER_NAME = "mcp-boilerplate";
|
|
8
|
-
export const SERVER_VERSION =
|
|
9
|
+
export const SERVER_VERSION = EMBEDDED_PACKAGE_VERSION;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Manufact quality score — gap list of this boilerplate
|
|
2
|
+
|
|
3
|
+
Every check Manufact runs for its MCP quality score, the verdict this boilerplate gets on it today, the
|
|
4
|
+
`mcp-doctor` rule that measures the same property, the evidence, and the boilerplate file that owns the fix.
|
|
5
|
+
|
|
6
|
+
| | |
|
|
7
|
+
|---|---|
|
|
8
|
+
| Boilerplate | `elpiarthera/vantage-mcp-boilerplate` `main` @ `a772895e836422973a5d8bd60277d62e511f629b` (package `0.7.0`) |
|
|
9
|
+
| Scanner (primary) | `mcp-doctor` 0.19.0 at PR #139 head `0b2f29e1d0222e09e47b8485154d237c4f98e223` (carries the S-49, S-50, C-37, C-38, A-13 detectors) |
|
|
10
|
+
| Scanner (comparison) | `npx -y @vantageos/mcp-doctor@0.19.0` (npm `latest`) |
|
|
11
|
+
| Served targets | the local server (`AUTH_MODE=public PORT=8787 npm run start:http`, `http://127.0.0.1:8787/mcp`) and the public instance `https://vantage-mcp-boilerplate-production.up.railway.app/mcp` (Railway, `AUTH_MODE=public`). Both serve `buildId` `e1db86666c3c032b` and the same 3 tools (`tools/list` byte-equal), so they run the same `dist/` |
|
|
12
|
+
| Check list | the 31 check ids of the Manufact report on vantage-peers (fixture `mcp-doctor/fixtures/manufact-vantage-peers-2026-11-08/manufact-report.json`), its 8 Server Metadata sub-checks, its Discoverability block, the policy review, and the domain/TLS publishing checks |
|
|
13
|
+
| Rule mapping | `ElPi-Corp/resources/references/mcp-manufact-gap.md` @ `7e5513eff22c0e2174c100d26e0aa40f642a562f` |
|
|
14
|
+
| Date | 2026-11-08 |
|
|
15
|
+
|
|
16
|
+
## Limits of this measurement
|
|
17
|
+
|
|
18
|
+
- **Manufact's engine is closed.** The open `mcp-use` code holds none of Manufact's checks. The only exact
|
|
19
|
+
value it publishes is the CLI score weighting, error 3 / warning 2 / info 1
|
|
20
|
+
(`mcp-use/mcp-use@ac0b6a53` `libraries/typescript/packages/cli/src/cloud/checklist/output.ts:85-105`).
|
|
21
|
+
Every verdict below is therefore our reading of the check's name, message and fix text, measured with
|
|
22
|
+
`mcp-doctor` or a raw wire read. It is never Manufact's own output.
|
|
23
|
+
- **Served checks ran twice**: on the local public-mode server and on the public Railway instance
|
|
24
|
+
(`https://vantage-mcp-boilerplate-production.up.railway.app/mcp`). Every `mcp-doctor` served verdict is identical on both, and so is every raw read
|
|
25
|
+
quoted below. The host-level checks (TLS, redirect, server card at the origin, setup page at the MCP URL)
|
|
26
|
+
were read on the public instance. The instance runs on Railway's `*.up.railway.app` hostname, which is not
|
|
27
|
+
an owned domain, and no directory submission has been made yet. No row is left `needs-public-instance`.
|
|
28
|
+
- A check passes only when a scanner output or a raw read-back proves it. `could-not-judge` means the property
|
|
29
|
+
is measured but Manufact's closed reading of it is ambiguous. The reason is stated on the row.
|
|
30
|
+
|
|
31
|
+
## Verdicts
|
|
32
|
+
|
|
33
|
+
Verdict values: `pass`, `fail`, `could-not-judge`, `needs-public-instance`. The `Sev.` column gives
|
|
34
|
+
Manufact's severity (error / warning / info) where the report states one.
|
|
35
|
+
|
|
36
|
+
### The 31 Manufact checks
|
|
37
|
+
|
|
38
|
+
| Check id | Name | Sev. | Verdict | mcp-doctor rule -> verdict | Evidence | Owning file (non-pass) |
|
|
39
|
+
|---|---|---|---|---|---|---|
|
|
40
|
+
| server-responds | MCP negotiation succeeds (v1/v2) | error | pass | S23 -> present (served); D3/D4 -> could-not-judge (the doctor obtained only a 2025-06-18 session) | `server/discover` at 2026-07-28 -> `supportedVersions:["2026-07-28"]`, `resultType:"complete"`, `ttlMs:300000`; `initialize` at 2025-11-25 -> HTTP 200, `protocolVersion:"2025-11-25"` | — |
|
|
41
|
+
| tools-list-responds | Tools list returns results | warning | pass | S12 -> present (served) | S12: "tools/list served 3 entries in one page with no nextCursor"; modern `tools/list` -> `ping`, `list_items`, `whoami` | — |
|
|
42
|
+
| skills-over-mcp-readable | Skills over MCP resources are readable | error | could-not-judge | C29 -> absent | Manufact answers `skip` when the extension is not declared. C29: "the extension io.modelcontextprotocol/skills is not declared … 1 SKILL.md file(s) sit in the tree (skills/connect-mcp-server/SKILL.md) and are not served". Whether a `skip` costs score is not public | `src/mcpServerFactory.ts` (declare `io.modelcontextprotocol/skills` and serve `skills/connect-mcp-server/SKILL.md`), if the skip is scored |
|
|
43
|
+
| tool-description-present | All tools have descriptions | warning | pass | C19 -> present (served) | C19: "3 served tool description(s) are bilingual, under the declared per-tool budget (700), and carry their trigger phrase" | — |
|
|
44
|
+
| tool-hints-present | All tools have OpenAI behavior hints | error | pass | C15 -> present (static) | C15: "3 tool(s) each declare all four behavioral annotations"; served, all 3 tools: `readOnlyHint:true`, `destructiveHint:false`, `openWorldHint:false`, all booleans | — |
|
|
45
|
+
| resources-list-responds | Resources list responds | info | pass | S12 -> present (served) | S12: "resources/list served 1 entries"; modern `resources/list` -> `ui://mcp-boilerplate/items-widget.html` | — |
|
|
46
|
+
| claude-code-metadata-length | Claude Code metadata fits its 2 KB fields | warning | pass | C38 -> present (served) | C38: "every budgeted field at or under its cap for claude-code (4 field(s) measured over 3 served tool(s), max 710 bytes)"; served `instructions` = 49 UTF-8 bytes | — |
|
|
47
|
+
| tool-title-present | All tools have a display title | error | pass | C35 -> present (served) | served `title`: `Ping`, `List items`, `Who am I`; C35: "titles and hints equal to the served ones" | — |
|
|
48
|
+
| claude-tool-hints-present | Claude tools declare applicable behavior hints | error | pass | C15 -> present (static) | served `readOnlyHint` and `destructiveHint` on all 3 tools | — |
|
|
49
|
+
| tool-input-header-annotations-valid | Tool input header annotations are valid | error | pass | no rule | no served tool carries `x-mcp-header`: nothing to invalidate (raw read of the 3 served tools) | — |
|
|
50
|
+
| openai-file-params-schema-valid | OpenAI file parameters use valid schemas | error | pass | no rule | no served tool declares an OpenAI file parameter. The `_meta` keys served are `ui`, `openai/outputTemplate`, `openai/toolInvocation/invoking`, `openai/toolInvocation/invoked`, `description_fr` on `list_items`, none on the other two | — |
|
|
51
|
+
| tool-name-protocol-length | Tool names follow the MCP 1–128 character range | warning | pass | DServer2 -> present (served) | served name lengths 4 / 10 / 6 | — |
|
|
52
|
+
| tool-input-schema-type | Tool input schemas have a valid object structure | error | pass | S43 -> present (served inputSchema read) | served `inputSchema.type` = `"object"` on all 3 tools | — |
|
|
53
|
+
| tool-name-format | Tool names use valid characters | warning | pass | DServer2 -> present (served) | each served name matches `[A-Za-z0-9_.-]{1,128}`; DServer2: "3 runtime tool name(s) snake_case" | — |
|
|
54
|
+
| tool-name-length | Claude tool names are within 64 characters | error | pass | DServer2 -> present (served) | longest served name `list_items`, 10 characters | — |
|
|
55
|
+
| cursor-tool-name-budget | Cursor tool-name budget advisory | warning | pass | no rule | longest served name 10 characters, against the 60-character combined budget | — |
|
|
56
|
+
| tool-names-protocol-unique | Tool names follow MCP uniqueness guidance | warning | pass | C12 -> present (static) | 3 served names, 3 distinct | — |
|
|
57
|
+
| tool-names-unique | ChatGPT tool names are unique | error | pass | C12 -> present (static) | 3 served names, 3 distinct | — |
|
|
58
|
+
| tool-output-schema-valid | Declared output schemas have a valid basic structure | error | pass | S44 -> present (served) | S44: "3 output schema(s) read (served tools/list outputSchema)"; each `type:"object"` | — |
|
|
59
|
+
| server-tools-capability-declared | Server declares tools capability | error | pass | S49 -> capabilities found (served) | `server/discover` -> `capabilities:{"tools":{"listChanged":true},"resources":{"listChanged":true}}` | — |
|
|
60
|
+
| tool-text-hidden-characters | Tool text has no hidden control characters | warning | pass | no rule | raw scan of the served name, title, description, inputSchema and annotations of the 3 tools for C0/C1 controls (tab, LF, CR excepted), U+200B–U+200F, U+202A–U+202E, U+2060–U+2064, U+FEFF, U+00AD: 0 hits | — |
|
|
61
|
+
| tool-resource-metadata-complete | View resource metadata complete | warning | fail | no rule | served resource `_meta.ui` = `{csp, prefersBorder}`. No `_meta.ui.widgetDescription` and no `_meta["openai/widgetDescription"]`, on the resource contents or on the linked tool `list_items`. Manufact's fix text reads exactly those two keys | `src/ui/uiResource.ts:71` (`buildItemsUiResourceMeta`) and `src/ui/uiResource.ts:103` (`ITEMS_TOOL_META`) |
|
|
62
|
+
| view-resource-coverage | All discovered view resources were inspected | warning | pass | D6 -> present (static) | 1 `ui://` resource listed, 1 read with HTTP 200 and a non-empty `contents[0]` | — |
|
|
63
|
+
| resource-content-valid | View resource envelope is valid | error | pass | D6 -> present (static) | `resources/read` -> `uri:"ui://mcp-boilerplate/items-widget.html"`, `mimeType:"text/html;profile=mcp-app"`, `text` 191777 characters | — |
|
|
64
|
+
| tool-resource-uri-readable | Tool-linked resource URI is readable | error | pass | D6 -> present (static) | `list_items._meta.ui.resourceUri` = `ui://mcp-boilerplate/items-widget.html`, read OK | — |
|
|
65
|
+
| tool-invocation-metadata-present | Tool invocation metadata present | warning | could-not-judge | A13 -> present (served) | A13 accepts the flat or the nested form: "1 App entry tool(s), each declaring invoking and invoked: list_items". The boilerplate serves only the flat keys `openai/toolInvocation/invoking` / `…/invoked`. Manufact's fix text names an `openai/toolInvocation` object "with string fields invoking and invoked". Which form the closed engine reads is unknown | `src/ui/uiResource.ts:103` (`ITEMS_TOOL_META`: serve the nested object beside the flat keys) |
|
|
66
|
+
| tool-visibility-valid | Tool visibility metadata is valid | error | pass | no rule | served `list_items._meta.ui.visibility` = `["model","app"]` | — |
|
|
67
|
+
| view-domain-present | View resources declare a domain | error | fail | A11 -> could-not-judge (static) | Locally (`PUBLIC_BASE_URL` unset), no `_meta.ui.domain`. With `PUBLIC_BASE_URL=https://mcp.example.com` the server serves `domain:"7a930d27a3f5c4643da88c1794444840.claudemcpcontent.com"`, which is Claude's sandbox hostname. That is not the exact HTTPS origin ChatGPT requires ("one exact HTTPS origin such as https://app.example.com … via _meta.ui.domain (or openai/widgetDomain)"), and no `openai/widgetDomain` is served. The public instance serves no domain either: its `resources/list` `_meta.ui` = `{csp, prefersBorder}`, because `PUBLIC_BASE_URL` is not set there | `src/ui/uiResource.ts:71` (add `openai/widgetDomain` = the owned HTTPS origin, keep `ui.domain` for Claude); `src/http.ts:10` (derive it from `PUBLIC_BASE_URL`); the Railway service env (`PUBLIC_BASE_URL` = the owned origin) |
|
|
68
|
+
| view-csp-origin-coverage | CSP metadata covers literal external origins | warning | could-not-judge | A2 -> present (static) | A2: "0 network/frame target(s) in the UI code". The served widget text holds 2 literal origins, both vendored React strings and neither fetched: `https://react.dev/errors/` (the error-URL builder) and `http://www.w3.org/2000/svg` (the `createElementNS` namespace). All CSP lists are empty. Whether Manufact's static detector counts vendored literals is unknown | `src/ui/uiResource.ts:71` (CSP lists), only if Manufact flags the vendored literals |
|
|
69
|
+
| view-frame-domains-absent | Views do not request nested iframe domains | warning | pass | A2 -> present (static, frame 0) | served `csp.frameDomains` = `[]`, so no frame domain is requested. If the closed engine treats the presence of the empty key as a request, drop the key in `src/ui/uiResource.ts:71` | — |
|
|
70
|
+
| view-domain-responds | Declared domains respond | warning | pass | — | No domain is declared, locally or on the public instance, so nothing can fail to respond. Manufact passed this check on vantage-peers in the same state ("All declared domains are reachable" next to view-domain-present "missing domain"). The row must be re-read once view-domain-present is fixed | — |
|
|
71
|
+
|
|
72
|
+
### Server Metadata sub-checks (Manufact `protocolMetadata.metadataScore`, points in brackets)
|
|
73
|
+
|
|
74
|
+
| Check id | Name | Sev. | Verdict | mcp-doctor rule -> verdict | Evidence | Owning file (non-pass) |
|
|
75
|
+
|---|---|---|---|---|---|---|
|
|
76
|
+
| metadata:identity | Stable server name [10] | — | pass | S49 -> found (served) | S49: `found: name "mcp-boilerplate"` | — |
|
|
77
|
+
| metadata:title | Human-readable title [10] | — | fail | S49 -> non-conforming (served) | S49: "title absent" | `src/mcpServerFactory.ts:207` (`serverInfo` passed to `new McpServer`) |
|
|
78
|
+
| metadata:description | Useful description [20] | — | fail | S49 -> non-conforming (served) | S49: "description absent" | `src/mcpServerFactory.ts:207` |
|
|
79
|
+
| metadata:version | Server version [10] | — | pass | S49 -> found; D16 -> present (served) | D16: `running server serves version "0.7.0" pre-auth on initialize, matching package.json` | — |
|
|
80
|
+
| metadata:website | Public website [10] | — | fail | S49 -> non-conforming (served) | S49: "websiteUrl absent" | `src/mcpServerFactory.ts:207` |
|
|
81
|
+
| metadata:icon | Scalable or high-resolution icon [10] | — | fail | S49 -> non-conforming (served) | S49: "icons absent" | `src/mcpServerFactory.ts:207` (`icons`, plus an SVG or 128px asset served by `src/httpApp.ts`) |
|
|
82
|
+
| metadata:instructions | Server instructions [20] | — | could-not-judge | S49 -> found (served) | S49: "instructions 49 bytes". The served text is `Clonable MCP server boilerplate — see README.md`: it is present, but it only points to the README. Manufact scores whether the instructions are useful ("explain how an agent should use the server"), and that judgement is closed | `src/mcpServerFactory.ts:216` |
|
|
83
|
+
| metadata:capabilities | Declared capabilities [10] | — | pass | S49 -> found (served) | S49: `capabilities tools, resources` | — |
|
|
84
|
+
|
|
85
|
+
### Discoverability (Manufact `discoverability`)
|
|
86
|
+
|
|
87
|
+
| Check id | Name | Sev. | Verdict | mcp-doctor rule -> verdict | Evidence | Owning file (non-pass) |
|
|
88
|
+
|---|---|---|---|---|---|---|
|
|
89
|
+
| discoverability:manifest | `mcp.json` or MCP Server Card served | — | fail | S50 -> non-conforming (served) | S50: "no server card served (/mcp.json 404, /.well-known/mcp.json 404, /mcp/server-card 404, /.well-known/mcp-server-card 404, /.well-known/mcp/server-card.json 404)"; raw `GET /.well-known/ai-catalog.json` -> 404. Public instance: all 7 paths -> 404 (S50 and raw read) | `src/httpApp.ts` (serve the card before the `/mcp` gate, built from `mcp-server.json` through `src/serverManifest.ts`) |
|
|
90
|
+
| discoverability:landingPage | Setup instructions at the MCP URL | — | fail | S50 -> non-conforming (served) | S50: "the MCP base URL http://127.0.0.1:8787/mcp answers HTTP 405: no setup instructions"; raw `GET /mcp` (Accept text/html) -> 405 `{"code":-32000,"message":"Method not allowed."}`. Public instance: `GET https://vantage-mcp-boilerplate-production.up.railway.app/mcp` -> 405, and S50 reads the same. On the OAuth tier the same GET would hit the auth gate (Manufact read HTTP 401 on vantage-peers) | `src/httpApp.ts:267` (answer an unauthenticated `GET /mcp` with `Accept: text/html` with a bilingual setup page, ahead of the auth middleware at `src/httpApp.ts:174`/`:216`) |
|
|
91
|
+
| discoverability:chatgpt | Listed on ChatGPT | — | fail | C37 -> non-conforming (static) | C37: "no listing proof for chatgpt (the ChatGPT app directory)". Not submitted, so not listed | `mcp-server.json` (`distribution.listings.chatgpt`, after the submission) |
|
|
92
|
+
| discoverability:claude | Listed on Claude (Verified by Anthropic) | — | fail | C37 -> non-conforming (static) | C37: "no listing proof for claude (the Claude connector directory)". Not submitted, so not listed | `mcp-server.json` (`distribution.listings.claude`, after the submission) |
|
|
93
|
+
| discoverability:cursor | Cursor marketplace | — | fail | C37 -> non-conforming (static) | C37: "no listing proof for cursor (the Cursor marketplace)". Not submitted, so not listed | `mcp-server.json` (`distribution.listings.cursor`, after the submission) |
|
|
94
|
+
| discoverability:muse | Listed on Muse | — | fail | no rule (Muse is not a declared target) | Not submitted, so not listed. `mcp-server.json` `distribution.targets` = `["claude","claude-code","vscode","cursor","chatgpt"]`: Muse is not declared, so C37 does not read it | `mcp-server.json` (`distribution.targets` + `listings`, if Muse is targeted) |
|
|
95
|
+
|
|
96
|
+
### Policy review and domain checks
|
|
97
|
+
|
|
98
|
+
| Check id | Name | Sev. | Verdict | mcp-doctor rule -> verdict | Evidence | Owning file (non-pass) |
|
|
99
|
+
|---|---|---|---|---|---|---|
|
|
100
|
+
| policy-review | Directory-policy directives in tool descriptions (Manufact "Security & Policy": instruction-like tool copy) | — | could-not-judge | C16 -> absent (n/a); C19 -> present (served, requires the trigger phrase) | The served descriptions say "Use this whenever you need to check that this server answers, even if they don't say 'ping'." (`ping`), "Use this whenever the user asks to list, see or render the items, even if they don't say 'list'." (`list_items`), and the same pattern on `whoami`. The Anthropic Software Directory Policy (Instructional Software) says (A) tools are defined "through narrow, unambiguous natural language", and (D) descriptions "must not be written in a way that intentionally leads to other Software extraneously calling them". The "even if they don't say X" widening is the copy a policy agent can flag. C-19 currently REQUIRES that trigger phrase, so removing it needs a standard ruling first | `src/mcpServerFactory.ts:261`, `:321`, `:390` (the three bilingual descriptions); the C-19 trigger-phrase clause in the standard |
|
|
101
|
+
| domain-tls | Widgets resolve from an owned domain: hostname, TLS, redirects (Manufact "The Domain is Set") | — | fail | S45 -> could-not-judge (static) | TLS and redirect hold on the public instance: TLSv1.3, certificate `CN=*.up.railway.app` issued by Let's Encrypt and expiring 2026-12-26; `http://…/health` -> 301 to `https://…/health`. The hostname is Railway's, though, not an owned one. S45: "the public origin is not declared: only a development default (localhost at src/config.ts:39)", and the widget serves no domain | the Railway service (an owned custom domain); `mcp-server.json` / `.env.example` (`PUBLIC_BASE_URL` = that origin) |
|
|
102
|
+
|
|
103
|
+
## Non-pass rows — the fix list
|
|
104
|
+
|
|
105
|
+
`fail`, fixable in the tree now:
|
|
106
|
+
|
|
107
|
+
1. `tool-resource-metadata-complete`: `src/ui/uiResource.ts:71`, `:103`.
|
|
108
|
+
2. `view-domain-present`: `src/ui/uiResource.ts:71`, `src/http.ts:10`, and `PUBLIC_BASE_URL` set on the Railway service.
|
|
109
|
+
3. `metadata:title`, `metadata:description`, `metadata:website`, `metadata:icon`: `src/mcpServerFactory.ts:207` (and an icon asset served by `src/httpApp.ts`).
|
|
110
|
+
4. `discoverability:manifest`: `src/httpApp.ts`, `src/serverManifest.ts`.
|
|
111
|
+
5. `discoverability:landingPage`: `src/httpApp.ts:267` (before the gates at `:174` / `:216`).
|
|
112
|
+
6. `domain-tls`: an owned custom domain on the Railway service, declared as `PUBLIC_BASE_URL` (`mcp-server.json` / `.env.example`). This is infrastructure, not code.
|
|
113
|
+
7. `discoverability:chatgpt`, `:claude`, `:cursor`, `:muse`: submit to each directory, then record the proof in `mcp-server.json` `distribution.listings` (C-37). This needs the owned domain and the fixes above first.
|
|
114
|
+
|
|
115
|
+
`could-not-judge`, where a defensive fix settles the ambiguity:
|
|
116
|
+
|
|
117
|
+
8. `tool-invocation-metadata-present`: `src/ui/uiResource.ts:103` (add the nested `openai/toolInvocation` object).
|
|
118
|
+
9. `metadata:instructions`: `src/mcpServerFactory.ts:216` (actionable instructions, not a README pointer).
|
|
119
|
+
10. `policy-review`: `src/mcpServerFactory.ts:261`, `:321`, `:390`. This needs a ruling on the C-19 trigger phrase first.
|
|
120
|
+
11. `skills-over-mcp-readable`: `src/mcpServerFactory.ts` (serve the Skills extension), only if a skip is scored.
|
|
121
|
+
12. `view-csp-origin-coverage`: `src/ui/uiResource.ts:71`, only if the vendored React literals are flagged.
|
|
122
|
+
|
|
123
|
+
## What the scanners could not read
|
|
124
|
+
|
|
125
|
+
- **`mcp-doctor --url` never opens a 2026-07-28 session.** D3 and D4 are could-not-judge: "the session
|
|
126
|
+
negotiated 2025-06-18 … the doctor could not obtain a 2026-07-28 session". The raw read shows the server
|
|
127
|
+
does serve 2026-07-28. `server/discover` with the per-request envelope (`_meta` protocolVersion +
|
|
128
|
+
clientCapabilities, `Mcp-Method` header) answers `resultType:"complete"`, `ttlMs`, `cacheScope`.
|
|
129
|
+
- **C25 is non-conforming in both served runs** (not a Manufact check): `served build-identity "buildId"="e1db86666c3c032b" DIFFERS from the expected commit "a772895e…"`.
|
|
130
|
+
The boilerplate's `buildId` is a content hash of `dist/` by design (`src/buildIdentity.ts`), while
|
|
131
|
+
`--source` makes the doctor expect the git commit. The two contracts disagree, and that disagreement is
|
|
132
|
+
traced separately from this list.
|
|
133
|
+
- **No `mcp-doctor` rule reads** `tool-resource-metadata-complete` (widgetDescription),
|
|
134
|
+
`tool-input-header-annotations-valid`, `openai-file-params-schema-valid`, `cursor-tool-name-budget`,
|
|
135
|
+
`tool-visibility-valid` or `tool-text-hidden-characters`. Those rows rest on a raw wire read, cited on each
|
|
136
|
+
row.
|
|
137
|
+
- **Static could-not-judge rows that bear on these checks**: A11 (the domain derivation exists, but its value
|
|
138
|
+
is judged only when served), S45 (no public origin declared), S49 and S50 (judged on the served half, which
|
|
139
|
+
is above), and C38 (judged served, above).
|
|
140
|
+
- **The public-instance runs** (`--url https://vantage-mcp-boilerplate-production.up.railway.app/mcp`, PR #139 and npm 0.19.0) return the same states as the
|
|
141
|
+
local runs: PR #139 gives present 13 / non-conforming 3 (C25, S49, S50) / could-not-judge 6, and npm gives
|
|
142
|
+
present 11 / non-conforming 1 / could-not-judge 6.
|
|
143
|
+
- **npm `latest` 0.19.0 does not carry** A13, C37, C38, S49 or S50. Its static run has 118 rows against 123
|
|
144
|
+
for PR #139, and its served run has 18 against 22. Every row the two versions share has the same verdict.
|
|
145
|
+
|
|
146
|
+
## How to reproduce
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
git clone https://github.com/elpiarthera/vantage-mcp-boilerplate.git bp && cd bp
|
|
150
|
+
git checkout a772895e836422973a5d8bd60277d62e511f629b
|
|
151
|
+
npm ci && npm run build && npm run build:ui
|
|
152
|
+
AUTH_MODE=public PORT=8787 npm run start:http &
|
|
153
|
+
|
|
154
|
+
# mcp-doctor at PR #139 head (0b2f29e1), built in ../md
|
|
155
|
+
node ../md/dist/doctor.js . --mode=static --json > static.json
|
|
156
|
+
node ../md/dist/doctor.js --url http://127.0.0.1:8787/mcp --source . --json > served.json
|
|
157
|
+
npx -y @vantageos/mcp-doctor@0.19.0 . --mode=static --json > static-npm.json
|
|
158
|
+
npx -y @vantageos/mcp-doctor@0.19.0 --url http://127.0.0.1:8787/mcp --source . --json > served-npm.json
|
|
159
|
+
|
|
160
|
+
# raw 2026-07-28 reads (server/discover, tools/list, resources/list, resources/read)
|
|
161
|
+
M='"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"probe","version":"0"},"io.modelcontextprotocol/clientCapabilities":{}}'
|
|
162
|
+
for m in server/discover tools/list resources/list; do
|
|
163
|
+
curl -s -X POST http://127.0.0.1:8787/mcp -H 'content-type: application/json' \
|
|
164
|
+
-H 'accept: application/json, text/event-stream' -H 'mcp-protocol-version: 2026-07-28' \
|
|
165
|
+
-H "mcp-method: $m" -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$m\",\"params\":{$M}}"; echo
|
|
166
|
+
done
|
|
167
|
+
curl -s -X POST http://127.0.0.1:8787/mcp -H 'content-type: application/json' \
|
|
168
|
+
-H 'accept: application/json, text/event-stream' -H 'mcp-protocol-version: 2026-07-28' \
|
|
169
|
+
-H 'mcp-method: resources/read' -H 'mcp-name: ui://mcp-boilerplate/items-widget.html' \
|
|
170
|
+
-d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"resources/read\",\"params\":{\"uri\":\"ui://mcp-boilerplate/items-widget.html\",$M}}"
|
|
171
|
+
curl -s -o /dev/null -w '%{http_code}\n' -H 'accept: text/html' http://127.0.0.1:8787/mcp # 405
|
|
172
|
+
|
|
173
|
+
# public instance: the same doctor and raw reads with U=https://vantage-mcp-boilerplate-production.up.railway.app
|
|
174
|
+
node ../md/dist/doctor.js --url "$U/mcp" --source . --json > served-public.json
|
|
175
|
+
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' "http://${U#https://}/health" # 301 -> https
|
|
176
|
+
curl -sv -o /dev/null "$U/health" 2>&1 | grep -E 'SSL connection|subject:|issuer:|expire date'
|
|
177
|
+
|
|
178
|
+
# the domain served once a public origin is set
|
|
179
|
+
AUTH_MODE=public PORT=8788 PUBLIC_BASE_URL=https://mcp.example.com node dist/http.js &
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Count the verdicts on this file (verdict = 5th `|`-separated field of a check row):
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
awk -F'|' '$2 ~ /^ (metadata:|discoverability:|policy-review|domain-tls|[a-z]+-[a-z-]+ )/ && $5 ~ /pass|fail|could-not-judge|needs-public-instance/ {gsub(/ /,"",$5); n[$5]++} END {for (v in n) print v, n[v]}' docs/manufact-gap.md
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Résumé (FR)
|
|
189
|
+
|
|
190
|
+
Ce document relève l'écart entre le boilerplate (`main` @ `a772895e`) et chacun des contrôles du score
|
|
191
|
+
qualité Manufact : les 31 contrôles du rapport, les 8 sous-contrôles « Server Metadata », la
|
|
192
|
+
découvrabilité, la revue de politique et les contrôles de domaine. Chaque ligne donne le verdict, la règle
|
|
193
|
+
`mcp-doctor` (PR #139 @ `0b2f29e1`), la preuve et le fichier propriétaire du correctif. Les échecs
|
|
194
|
+
corrigeables tout de suite portent sur les métadonnées serveur (title, description, websiteUrl, icons dans
|
|
195
|
+
`src/mcpServerFactory.ts`), la description et le domaine du widget (`src/ui/uiResource.ts`), la carte
|
|
196
|
+
serveur et la page d'installation à l'URL MCP (`src/httpApp.ts`). Les contrôles servis ont été lus sur le
|
|
197
|
+
serveur local et sur l'instance publique Railway, avec des verdicts identiques. TLS et la redirection vers
|
|
198
|
+
HTTPS tiennent, mais le nom d'hôte appartient à Railway et non à nous : il manque un domaine possédé. Aucune
|
|
199
|
+
inscription en annuaire n'a encore été faite. La revue de politique signale la formule « even if they don't say X » des descriptions, que la
|
|
200
|
+
règle C-19 impose aujourd'hui. La retirer demande d'abord un arbitrage sur la norme.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
A release is cut from `main`, in one pull request, after every change in it has merged.
|
|
4
|
+
|
|
5
|
+
1. **Fold the changelog.** `npm run changelog:release -- --version <x.y.z>` (optionally `--date YYYY-MM-DD`)
|
|
6
|
+
inserts `## [x.y.z] - <date>` below `## [Unreleased]`, with every fragment of `changelog.d/` grouped by
|
|
7
|
+
section, and deletes the folded fragments. `## [Unreleased]` stays, empty. Then add the version's link line
|
|
8
|
+
at the bottom of `CHANGELOG.md` (`[x.y.z]: <url>`, the npm page of the version when it is published without
|
|
9
|
+
a git tag); `tests/changelogLinks.test.ts` refuses a version heading with no link line.
|
|
10
|
+
2. **Bump the version** in `package.json` and `mcp-server.json` (they must be equal; `npm run build` refuses
|
|
11
|
+
a difference), then `npm install --package-lock-only` so the lockfile carries it.
|
|
12
|
+
3. **Build and test**: `npm run build && npm run build:ui && npm test`. `prepublishOnly` runs the same chain,
|
|
13
|
+
plus the evals, before any publish.
|
|
14
|
+
4. **Open the release PR** through the review flow. Publication happens only from the merged `main`, after
|
|
15
|
+
the reviewer's npm verdict, and is proven at the registry (full and abbreviated documents, then a fresh
|
|
16
|
+
consumer install), never by the publish command's output.
|
|
17
|
+
|
|
18
|
+
Between releases, every pull request adds one fragment to `changelog.d/` (see `changelog.d/README.md`) and
|
|
19
|
+
never touches `CHANGELOG.md`, so parallel pull requests do not conflict on it.
|
package/mcp-server.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-boilerplate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"capabilities": {
|
|
5
5
|
"tools": {},
|
|
6
6
|
"resources": {}
|
|
@@ -10,5 +10,6 @@
|
|
|
10
10
|
"streamableHttp": { "path": "/mcp", "stateless": true }
|
|
11
11
|
},
|
|
12
12
|
"features": {},
|
|
13
|
-
"contextCeiling": { "global":
|
|
13
|
+
"contextCeiling": { "global": 1400, "perTool": 700 },
|
|
14
|
+
"distribution": { "targets": ["claude", "claude-code", "vscode", "cursor", "chatgpt"] }
|
|
14
15
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vantageos/mcp-boilerplate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"displayName": "MCP Boilerplate",
|
|
4
5
|
"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
6
|
"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
7
|
"type": "module",
|
|
@@ -53,11 +54,15 @@
|
|
|
53
54
|
"homepage": "https://github.com/elpiarthera/vantage-mcp-boilerplate#readme",
|
|
54
55
|
"author": "ElPi Corp / Laurent Perello",
|
|
55
56
|
"scripts": {
|
|
56
|
-
"build": "node scripts/clean-dist.mjs &&
|
|
57
|
+
"build": "node scripts/clean-dist.mjs && node scripts/embed-static.mjs && tsc && node scripts/write-build-info.mjs && node scripts/sync-plugin-version.mjs && node scripts/listing-kit.mjs --check",
|
|
58
|
+
"listing:write": "node scripts/listing-kit.mjs",
|
|
59
|
+
"changelog:check": "node scripts/changelog-assemble.mjs --check",
|
|
60
|
+
"changelog:release": "node scripts/changelog-assemble.mjs",
|
|
57
61
|
"prepack": "node scripts/check-dist-orphans.mjs",
|
|
58
62
|
"build:ui": "vite build && mv dist/ui-bundle/index.html dist/ui-bundle/widget.html",
|
|
59
63
|
"pretest": "npm run build && npm run build:ui",
|
|
60
64
|
"test": "vitest run",
|
|
65
|
+
"test:a11y": "vitest run tests/widgetAxe.test.ts",
|
|
61
66
|
"test:watch": "vitest",
|
|
62
67
|
"typecheck": "tsc --noEmit",
|
|
63
68
|
"start:http": "node dist/http.js",
|
|
@@ -83,7 +88,7 @@
|
|
|
83
88
|
"dependencies": {
|
|
84
89
|
"@modelcontextprotocol/ext-apps": "^2.0.3",
|
|
85
90
|
"@modelcontextprotocol/server": "^2.3.1",
|
|
86
|
-
"@vantageos/cloud-identity": "^0.
|
|
91
|
+
"@vantageos/cloud-identity": "^0.34.1",
|
|
87
92
|
"@vantageos/mosaic-i18n": "^0.1.1",
|
|
88
93
|
"hono": "^4.13.13",
|
|
89
94
|
"jose": "^6.2.7",
|
|
@@ -100,11 +105,14 @@
|
|
|
100
105
|
"devDependencies": {
|
|
101
106
|
"@biomejs/biome": "1.9.4",
|
|
102
107
|
"@modelcontextprotocol/client": "^2.3.1",
|
|
108
|
+
"@types/jsdom": "^30.0.0",
|
|
103
109
|
"@types/node": "^20.14.0",
|
|
104
110
|
"@types/react": "^19.0.0",
|
|
105
111
|
"@types/react-dom": "^19.0.0",
|
|
106
112
|
"@vitejs/plugin-react": "^4.3.0",
|
|
113
|
+
"axe-core": "^4.14.0",
|
|
107
114
|
"convex": "1.46.0",
|
|
115
|
+
"jsdom": "^30.1.3",
|
|
108
116
|
"react": "^19.0.0",
|
|
109
117
|
"react-dom": "^19.0.0",
|
|
110
118
|
"typescript": "^5.7.2",
|
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.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"
|
|
@@ -34,9 +34,7 @@
|
|
|
34
34
|
"longDescription": "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.",
|
|
35
35
|
"developerName": "ElPi Corp / Laurent Perello",
|
|
36
36
|
"websiteURL": "https://github.com/elpiarthera/vantage-mcp-boilerplate#readme",
|
|
37
|
-
"capabilities": [
|
|
38
|
-
"Read"
|
|
39
|
-
]
|
|
37
|
+
"capabilities": ["Read"]
|
|
40
38
|
}
|
|
41
39
|
}
|
|
42
40
|
}
|
|
@@ -31,5 +31,24 @@
|
|
|
31
31
|
"openai": {
|
|
32
32
|
"auth": { "type": "oauth", "client": { "mode": "cimd" } },
|
|
33
33
|
"capabilities": ["Read"]
|
|
34
|
+
},
|
|
35
|
+
"listing": {
|
|
36
|
+
"testPrompts": {
|
|
37
|
+
"positive": [
|
|
38
|
+
"tools-call-ping-happy-path",
|
|
39
|
+
"tools-call-ping-default",
|
|
40
|
+
"tools-call-list-items-public-tier-unscoped",
|
|
41
|
+
"tools-call-list-items-page-truncated",
|
|
42
|
+
"tools-call-list-items-ui-link"
|
|
43
|
+
],
|
|
44
|
+
"negative": [
|
|
45
|
+
"tools-call-ping-message-too-long-failure",
|
|
46
|
+
"tools-call-list-items-requires-tenant-refusal-failure",
|
|
47
|
+
"tools-call-list-items-invalid-cursor-failure"
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"reviewerAccount": {
|
|
51
|
+
"howToObtain": "Sign in at the server's /authorize page with any Clerk account; a reviewer organisation is created on request to the maintainers (README, 'Own authorization server'). No credential is stored in this repository."
|
|
52
|
+
}
|
|
34
53
|
}
|
|
35
54
|
}
|
package/scripts/readme-fr.mjs
CHANGED
|
@@ -27,7 +27,9 @@ import { fileURLToPath } from "node:url";
|
|
|
27
27
|
export const BANNER =
|
|
28
28
|
"<!-- Généré depuis README.md par mcp-readme-fr (@vantageos/mcp-boilerplate) : ne pas modifier à la main. -->";
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
// A marker sits at the start of a line with at most 3 spaces of indent (CommonMark): 4 or more spaces, or a tab, make
|
|
31
|
+
// the line an indented code block, so such a "marker" is text, copied with the part it sits in, like a fenced one.
|
|
32
|
+
const MARKER = /^ {0,3}<!--\s*(lang:([A-Za-z-]+)|\/lang)\s*-->\s*$/;
|
|
31
33
|
const FENCE = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
32
34
|
|
|
33
35
|
/** README.md text -> { fr, errors }. `fr` is the README.fr.md content (banner first). */
|
|
@@ -83,19 +85,24 @@ export function generate(text) {
|
|
|
83
85
|
return;
|
|
84
86
|
}
|
|
85
87
|
// lang === "fr"
|
|
86
|
-
if (state === "shared")
|
|
88
|
+
if (state === "shared") {
|
|
87
89
|
errors.push(
|
|
88
90
|
`line ${n}: lang:fr opens a region before its English part (<!-- lang:en --> first)`
|
|
89
91
|
);
|
|
90
|
-
|
|
92
|
+
// The region it opens is reported from this line, never from a stale or zero opening line.
|
|
93
|
+
openedAt = n;
|
|
94
|
+
} else if (state === "fr")
|
|
91
95
|
errors.push(`line ${n}: a second lang:fr in the region opened at line ${openedAt}`);
|
|
92
96
|
state = "fr";
|
|
93
97
|
});
|
|
94
98
|
if (fence !== null)
|
|
95
99
|
errors.push(`line ${fence.at}: unclosed code fence (${fence.char.repeat(fence.len)})`);
|
|
100
|
+
// Every line number printed is 1-based. The last line of a file ending with a newline is the one before the empty
|
|
101
|
+
// string that split() leaves after it.
|
|
102
|
+
const lastLine = text.endsWith("\n") ? lines.length - 1 : lines.length;
|
|
96
103
|
if (state !== "shared")
|
|
97
104
|
errors.push(
|
|
98
|
-
`line ${
|
|
105
|
+
`line ${lastLine}: the region opened at line ${openedAt} is unclosed (<!-- /lang -->)`
|
|
99
106
|
);
|
|
100
107
|
return { fr: out.join("\n"), errors };
|
|
101
108
|
}
|
package/dist/locales/en.json
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"Mcp": {
|
|
3
|
-
"auth": {
|
|
4
|
-
"unauthorized": "Unauthorized",
|
|
5
|
-
"originNotAllowed": "This origin is not allowed to call this endpoint"
|
|
6
|
-
},
|
|
7
|
-
"tenant": {
|
|
8
|
-
"permissionDenied": "permission denied: no tenant resolved for this caller"
|
|
9
|
-
},
|
|
10
|
-
"person": {
|
|
11
|
-
"identityNotConfigured": "permission denied: this tool needs the verified person, and this server resolves no person (identity is not configured)",
|
|
12
|
-
"noVerifiedSession": "permission denied: this tool needs the verified person, and this call carries no verified session",
|
|
13
|
-
"refused": "permission denied: the verified person behind this call could not be resolved"
|
|
14
|
-
},
|
|
15
|
-
"validation": {
|
|
16
|
-
"messageTooLong": "message exceeds the maximum length of 200 characters"
|
|
17
|
-
},
|
|
18
|
-
"oauth": {
|
|
19
|
-
"pickerTitle": "Connect {client}",
|
|
20
|
-
"pickerIntro": "{client} asks to act on your behalf. Choose the organisation it may reach, then approve or deny.",
|
|
21
|
-
"handoffTitle": "Returning to the application",
|
|
22
|
-
"handoffLink": "Continue to the application",
|
|
23
|
-
"pickerNoAllowedOrganisation": "None of your organisations is authorised on this server.",
|
|
24
|
-
"pickerDestination": "After approval you are sent to:",
|
|
25
|
-
"pickerScope": "Requested access:",
|
|
26
|
-
"pickerUnverified": "Unverified client: it registered itself automatically and nobody has vouched for it. Approve only if you recognise the address above.",
|
|
27
|
-
"pickerOrganisation": "Organisation",
|
|
28
|
-
"approve": "Approve",
|
|
29
|
-
"deny": "Deny",
|
|
30
|
-
"denied": "The request was denied; you can close this window.",
|
|
31
|
-
"unavailable": "Authorization is not configured on this server.",
|
|
32
|
-
"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.",
|
|
33
|
-
"consentTokenRequired": "The consent token is missing: reload the page and try again.",
|
|
34
|
-
"registryFull": "Client registration is temporarily unavailable: the registry is full. Retry later.",
|
|
35
|
-
"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\")."
|
|
36
|
-
},
|
|
37
|
-
"manifest": {
|
|
38
|
-
"unreadableField": "cannot read field \"{field}\" of {source} (expected: {expected})",
|
|
39
|
-
"expect": {
|
|
40
|
-
"string": "a non-empty string",
|
|
41
|
-
"semver": "a semantic version (x.y.z)",
|
|
42
|
-
"object": "an object",
|
|
43
|
-
"boolean": "true or false",
|
|
44
|
-
"positiveInteger": "a positive whole number",
|
|
45
|
-
"atLeastPerTool": "at least the perTool ceiling"
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
"toolRecord": {
|
|
49
|
-
"unsound": "tool records or the context ceiling are unsound: {problems}"
|
|
50
|
-
},
|
|
51
|
-
"pagination": {
|
|
52
|
-
"invalidCursor": "invalid cursor: it was not issued by this tool or is no longer valid; start again without a cursor"
|
|
53
|
-
},
|
|
54
|
-
"lake": {
|
|
55
|
-
"unreadable": "the data source answered something this server cannot read; no row is shown rather than a partial or wrong page"
|
|
56
|
-
},
|
|
57
|
-
"buildIdentity": {
|
|
58
|
-
"unreadable": "cannot compute the build identity: {dir} is unreadable ({reason})",
|
|
59
|
-
"empty": "cannot compute the build identity: {dir} is empty (no file), nothing was built"
|
|
60
|
-
},
|
|
61
|
-
"config": {
|
|
62
|
-
"refused": "configuration refused: {problems}",
|
|
63
|
-
"missing": "{env} is missing (expected: {expected})",
|
|
64
|
-
"invalid": "{env} is set but invalid (expected: {expected}; value not shown)",
|
|
65
|
-
"expect": {
|
|
66
|
-
"string": "a non-empty string",
|
|
67
|
-
"integer": "an integer from {min} to {max}",
|
|
68
|
-
"integerAtLeast": "an integer of at least {min}",
|
|
69
|
-
"boolean": "\"0\" or \"1\"",
|
|
70
|
-
"url": "an https URL (http on loopback only)",
|
|
71
|
-
"originList": "a comma-separated list of origins (scheme://host[:port], no path)",
|
|
72
|
-
"enum": "one of: {values}",
|
|
73
|
-
"json": "a JSON object or array"
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
package/dist/locales/fr.json
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"Mcp": {
|
|
3
|
-
"auth": {
|
|
4
|
-
"unauthorized": "Non autorisé",
|
|
5
|
-
"originNotAllowed": "Cette origine n'est pas autorisée à appeler ce point d'accès"
|
|
6
|
-
},
|
|
7
|
-
"tenant": {
|
|
8
|
-
"permissionDenied": "permission refusée : aucun tenant résolu pour cet appelant"
|
|
9
|
-
},
|
|
10
|
-
"person": {
|
|
11
|
-
"identityNotConfigured": "permission refusée : cet outil exige la personne vérifiée, et ce serveur ne résout aucune personne (identité non configurée)",
|
|
12
|
-
"noVerifiedSession": "permission refusée : cet outil exige la personne vérifiée, et cet appel ne porte aucune session vérifiée",
|
|
13
|
-
"refused": "permission refusée : la personne vérifiée derrière cet appel n'a pas pu être résolue"
|
|
14
|
-
},
|
|
15
|
-
"validation": {
|
|
16
|
-
"messageTooLong": "le message dépasse la longueur maximale de 200 caractères"
|
|
17
|
-
},
|
|
18
|
-
"oauth": {
|
|
19
|
-
"pickerTitle": "Connecter {client}",
|
|
20
|
-
"pickerIntro": "{client} demande à agir en votre nom. Choisissez l'organisation à laquelle il peut accéder, puis approuvez ou refusez.",
|
|
21
|
-
"handoffTitle": "Retour à l'application",
|
|
22
|
-
"handoffLink": "Continuer vers l'application",
|
|
23
|
-
"pickerNoAllowedOrganisation": "Aucune de vos organisations n'est autorisée sur ce serveur.",
|
|
24
|
-
"pickerDestination": "Redirection après validation vers :",
|
|
25
|
-
"pickerScope": "Accès demandé :",
|
|
26
|
-
"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.",
|
|
27
|
-
"pickerOrganisation": "Organisation",
|
|
28
|
-
"approve": "Approuver",
|
|
29
|
-
"deny": "Refuser",
|
|
30
|
-
"denied": "La demande a été refusée ; vous pouvez fermer cette fenêtre.",
|
|
31
|
-
"unavailable": "L'autorisation n'est pas configurée sur ce serveur.",
|
|
32
|
-
"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.",
|
|
33
|
-
"consentTokenRequired": "Le jeton de consentement est absent : rechargez la page et réessayez.",
|
|
34
|
-
"registryFull": "L'enregistrement de clients est temporairement indisponible : le registre est plein. Réessayez plus tard.",
|
|
35
|
-
"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 »)."
|
|
36
|
-
},
|
|
37
|
-
"manifest": {
|
|
38
|
-
"unreadableField": "champ \"{field}\" illisible dans {source} (attendu : {expected})",
|
|
39
|
-
"expect": {
|
|
40
|
-
"string": "une chaîne non vide",
|
|
41
|
-
"semver": "une version sémantique (x.y.z)",
|
|
42
|
-
"object": "un objet",
|
|
43
|
-
"boolean": "true ou false",
|
|
44
|
-
"positiveInteger": "un entier strictement positif",
|
|
45
|
-
"atLeastPerTool": "au moins le plafond perTool"
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
"toolRecord": {
|
|
49
|
-
"unsound": "fiches d'outils ou plafond de contexte invalides : {problems}"
|
|
50
|
-
},
|
|
51
|
-
"pagination": {
|
|
52
|
-
"invalidCursor": "curseur invalide : il n'a pas été émis par cet outil ou n'est plus valide ; recommencez sans curseur"
|
|
53
|
-
},
|
|
54
|
-
"lake": {
|
|
55
|
-
"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"
|
|
56
|
-
},
|
|
57
|
-
"buildIdentity": {
|
|
58
|
-
"unreadable": "identité de build incalculable : {dir} est illisible ({reason})",
|
|
59
|
-
"empty": "identité de build incalculable : {dir} est vide (aucun fichier), rien n'a été construit"
|
|
60
|
-
},
|
|
61
|
-
"config": {
|
|
62
|
-
"refused": "configuration refusée : {problems}",
|
|
63
|
-
"missing": "{env} est manquante (attendu : {expected})",
|
|
64
|
-
"invalid": "{env} est définie mais invalide (attendu : {expected} ; valeur non affichée)",
|
|
65
|
-
"expect": {
|
|
66
|
-
"string": "une chaîne non vide",
|
|
67
|
-
"integer": "un entier de {min} à {max}",
|
|
68
|
-
"integerAtLeast": "un entier d'au moins {min}",
|
|
69
|
-
"boolean": "\"0\" ou \"1\"",
|
|
70
|
-
"url": "une URL https (http seulement sur la boucle locale)",
|
|
71
|
-
"originList": "une liste d'origines séparées par des virgules (schéma://hôte[:port], sans chemin)",
|
|
72
|
-
"enum": "une valeur parmi : {values}",
|
|
73
|
-
"json": "un objet ou un tableau JSON"
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|