From baf4398bdd988bfca80a2630c9d901b3c88007da Mon Sep 17 00:00:00 2001 From: sycatle Date: Tue, 11 Aug 2026 16:22:16 +0200 Subject: [PATCH] docs: English README, and the CI this package never had The package is public and its README was French while its GitHub description was English. It now opens on how to install it, states that the SDK and zod are peer dependencies and why, and keeps the section that matters: the oracle proving the sharing is real, where breaking one byte here turns both consuming servers red. The CI notes what makes this package different from the servers that use it -- it has no game, no Wine and no sidecar to miss, so nothing skips and a green run means the whole suite ran. --- .github/workflows/ci.yml | 35 ++++++++++++ README.md | 111 ++++++++++++++++++++------------------- 2 files changed, 93 insertions(+), 53 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..dffbe99 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node: ["20", "22"] + + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 10 + + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: pnpm + + - run: pnpm install --frozen-lockfile + - run: pnpm build + - run: pnpm typecheck + + # Unlike the servers that consume it, this package has no external dependencies to + # miss: no game, no Wine, no Python. Nothing here skips, so the whole suite runs on + # a bare runner and a green result means what it says. + - run: pnpm test diff --git a/README.md b/README.md index 9f68fa1..54b0224 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,85 @@ # @projectsociety/mcp-core -Plomberie MCP partagée : registre d'outils, garde de confirmation, journal d'audit, -chargement de configuration, exécution de processus. +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) + +Shared MCP plumbing: tool registry, confirmation guard, audit log, config loading, process +running. ```bash npm install @projectsociety/mcp-core ``` -Extrait de deux serveurs MCP réels — [`hammer-mcp`](https://github.com/ProjectSocietyStudio/hammer-mcp) -et `gmod-mcp` — plutôt que conçu dans l'abstrait. Ce qui suit dit pourquoi, et surtout ce -qui est délibérément resté chez les serveurs. +**Extracted from two real MCP servers rather than designed in the abstract** — +[`hammer-mcp`](https://github.com/ProjectSocietyStudio/hammer-mcp), which drives the Source-engine +toolchain, and `gmod-mcp`, which drives a running game server. What follows says why it exists, and +above all what deliberately stayed behind in each server. -Node ≥ 20. `@modelcontextprotocol/sdk` ≥ 1.30 et `zod` ^3.23 sont des **peer dependencies** : -c'est le serveur hôte qui les fournit, pour qu'il n'existe qu'une seule instance du SDK. +Node ≥ 20. `@modelcontextprotocol/sdk` ≥ 1.30 and `zod` ^3.23 are **peer dependencies**: the host +server provides them, so there is only ever one instance of the SDK. -## Pourquoi il existe +## Why it exists -Les deux serveurs ont d'abord dupliqué cette plomberie **délibérément** — ~350 lignes entre -deux dépôts, un paquet partagé aurait été prématuré. Le README de `hammer-mcp` avait écrit -le seuil de révision : « un troisième serveur MCP, ou le même bug de plomberie corrigé deux -fois ». +The two servers duplicated this plumbing **on purpose** — around 350 lines across two repositories, +where a shared package looked premature. The revision threshold was written down at the time: *a +third MCP server, or the same plumbing bug fixed twice.* -Ce seuil a été atteint le 11/08/2026, sur deux fronts à la fois : +It was reached on 11/08/2026, on two fronts at once: -1. **La dérive était déjà là.** `clip()` vivait dans le `registry.ts` de hammer-mcp et se - retrouvait recopiée deux fois côté gmod-mcp (`tools/local.ts`, `tools/dev.ts`) ; - `stripAnsi()` n'existait que d'un côté, le bloc image `IMAGE_KEY` que de l'autre. Trois - divergences sur six fichiers. -2. **La montée du SDK arrivait.** Passer de `^1.12` à `1.30` — et exploiter ce qu'elle - débloque (`outputSchema`, `serverInstructions`, elicitation, notifications de - progression) — aurait été à faire et à prouver deux fois. +1. **The drift was already there.** `clip()` lived in one server's registry and had been copied + twice into the other; `stripAnsi()` existed on only one side, the image block on only the other. + Three divergences across six files. +2. **An SDK upgrade was coming.** Going from `^1.12` to `1.30` — and using what it unlocks + (`outputSchema`, `serverInstructions`, elicitation, progress notifications) — would have had to + be done and proven twice. -## Ce qui monte, ce qui ne monte pas +## What moves up, and what does not -| Monte | Reste chez le serveur | +| Moves up | Stays in the server | |---|---| -| `ToolRegistry`, `defineTool`, `isCallAllowed` | `fs/guard.ts` (hammer) — discipline d'écriture propre à ses arbres | -| `createMcpServer`, `successResult`, blocs image | `bridge/`, `bridge/lock.ts` (gmod) — le verrou et le transport fichier | -| `AuditLog` | `patch/engine.ts` (gmod) — sauvegarde/restauration de fichiers | -| `loadConfig`, `findRepoRoot` | le **schéma** de config de chacun | -| `run`, `clip`, `stripAnsi` | l'enum `Realm` de chacun — `map`/`local` contre `sv`/`cl`/`local` | +| `ToolRegistry`, `defineTool`, `isCallAllowed` | its write guard — the trees it refuses are its own | +| `createMcpServer`, `successResult`, image blocks | the file transport and lock of the live-engine server | +| `AuditLog` | file backup and restore | +| `loadConfig`, `findRepoRoot` | each server's config **schema** | +| `run`, `clip`, `stripAnsi` | each server's `Realm` enum — `map`/`local` against `sv`/`cl`/`local` | + +The core is **parameterised, not generic on principle**: `loadConfig` takes the environment +variable and the state directory name; `AuditLog` takes the server's own vocabulary of entry kinds; +`ToolDef` is generic over context and realm, so each server keeps its own typed `defineTool` via +`makeToolkit`. + +`installProject` takes the entry-point path **as a parameter**. That is the trap of extraction: the +original computed it with `new URL("./index.js", import.meta.url)`, which, moved here, would have +resolved to the core's own `dist/` and installed the wrong binary. + +## The oracle that proves the sharing is real + +A passing test proves nothing until it has been shown it can fail — and a "shared" package that was +in fact copied would pass every test exactly the same way. -Le noyau est **paramétré, pas générique par principe** : `loadConfig` prend la variable -d'environnement et le nom du répertoire d'état ; `AuditLog` prend le vocabulaire de kinds -du serveur ; `ToolDef` est générique sur le contexte et sur le realm, si bien que chaque -serveur garde son propre `defineTool` typé via `makeToolkit`. +So the control is direct: break the guard in `src/registry.ts`, rebuild, and run all three suites. +Measured 11/08/2026: -`installProject` prend le chemin du point d'entrée **en paramètre**. C'est le piège de -l'extraction : le code d'origine le calculait par `new URL("./index.js", import.meta.url)`, -qui, déplacé ici, aurait résolu vers le `dist/` du noyau et installé le mauvais binaire. +| Suite | Result | +|---|---| +| `mcp-core` | 2 failures / 21 | +| `hammer-mcp` | 1 failure / 46 | +| `gmod-mcp` | 2 failures / 130 | + +One byte changed here turns both servers red. That is the property the extraction was for. -## Développement +## Development ```bash pnpm install pnpm build # tsc -> dist/ pnpm test # vitest -pnpm typecheck # tsc --noEmit, tests inclus +pnpm typecheck # tsc --noEmit, tests included ``` -Les consommateurs lisent le `dist/` publié. En développement local — un checkout des -serveurs à côté de celui-ci, consommé par `file:../mcp-core` — **reconstruire est -nécessaire** pour qu'un changement leur parvienne. Le piège se paie une fois : un `dist/` -périmé rend les tests verts et le typecheck rouge sur le même arbre. - -## L'oracle qui prouve le partage +Consumers read the published `dist/`. In local development — server checkouts beside this one, +consuming it through `file:../mcp-core` — **rebuilding is required** for a change to reach them. +The trap is paid once: a stale `dist/` makes tests green and typecheck red on the same tree. -Un test qui passe ne prouve rien tant qu'on n'a pas montré qu'il sait échouer, et un -paquet « partagé » qui serait en réalité recopié passerait tous les tests de la même façon. -Le contrôle est donc direct : neutraliser la garde dans `src/registry.ts`, reconstruire, et -lancer les trois suites. Mesuré le 11/08/2026 — - -| Suite | Résultat | -|---|---| -| `mcp-core` | 2 échecs / 21 | -| `hammer-mcp` | 1 échec / 46 | -| `gmod-mcp` | 2 échecs / 130 | +## License -Un seul octet changé ici fait rougir les deux serveurs. C'est la propriété qu'on voulait. +MIT. See [LICENSE](LICENSE).