diff --git a/CHANGELOG.md b/CHANGELOG.md index 104b02f..3e6107e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,16 @@ The project publishes 0.x prerelease versions; a stable release line is not yet - Migrate GitHub repository, Release, issue, badge, and raw-content coordinates to the canonical `bytefolk` organization while retaining the published npm - scope, MCP identity, and existing cache paths. + scope and the existing cache paths. +- Follow the registry identifier after that rename: `mcpName` becomes + `io.github.bytefolk/mem-mcp`, because the official MCP Registry namespace is + derived from the repository owner and the previous value, naming the + organization this repository used to belong to, cannot resolve. The npm + package name and the installer's cache directory are deliberately unchanged, + so an existing installation keeps working and keeps its cache. + `npm/registry-identity.test.js` now asserts the identifier against the + repository coordinate the installer itself uses, so the next rename cannot + leave a stale identifier behind unnoticed. ### Security diff --git a/npm/install.js b/npm/install.js index 55dcf41..4215cc7 100644 --- a/npm/install.js +++ b/npm/install.js @@ -792,6 +792,7 @@ module.exports = { isLockContention, openResponse, releaseAssetLock, + REPO, sha256File, verifyFile, }; diff --git a/npm/package.json b/npm/package.json index 9481628..235e2ee 100644 --- a/npm/package.json +++ b/npm/package.json @@ -2,7 +2,7 @@ "name": "@fullstack-ai-infra/mem-mcp", "version": "0.1.1", "description": "MCP server for mem — a portable, self-hosted memory plane for AI agents", - "mcpName": "io.github.fullstack-ai-infra/mem-mcp", + "mcpName": "io.github.bytefolk/mem-mcp", "keywords": [ "mcp", "model-context-protocol", @@ -27,7 +27,7 @@ "os": ["linux", "darwin", "win32"], "cpu": ["x64", "arm64"], "scripts": { - "test": "node --test install.test.js mem-mcp.test.js windows-shim.test.js", + "test": "node --test install.test.js mem-mcp.test.js registry-identity.test.js windows-shim.test.js", "test:tarball": "node --test clean-tarball.test.js" }, "files": [ diff --git a/npm/registry-identity.test.js b/npm/registry-identity.test.js new file mode 100644 index 0000000..298e161 --- /dev/null +++ b/npm/registry-identity.test.js @@ -0,0 +1,60 @@ +"use strict"; + +const assert = require("node:assert/strict"); +const { readFileSync } = require("node:fs"); +const { join } = require("node:path"); +const test = require("node:test"); +const { REPO } = require("./install"); + +const serverManifest = JSON.parse( + readFileSync(join(__dirname, "server.json"), "utf8"), +); +const packageManifest = JSON.parse( + readFileSync(join(__dirname, "package.json"), "utf8"), +); + +// The registry derives its namespace from the repository owner, so every rename +// leaves the published identifier pointing at an organization that no longer +// exists, and the submission is rejected with no hint that a stale string in a +// manifest caused it. The repository coordinate is therefore asserted here +// against the identifier the submission carries, rather than repeated in a +// third file. +test("the registry namespace follows the repository owner", () => { + const [owner] = REPO.split("/"); + assert.match(REPO, /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\/[a-z0-9._-]+$/i); + for (const [label, manifest] of [ + ["server.json", serverManifest], + ["package.json", packageManifest], + ]) { + assert.equal( + manifest.mcpName, + `io.github.${owner}/${serverManifest.name}`, + `${label} mcpName must carry the owner of the repository the installer downloads from (${REPO})`, + ); + } +}); + +// Both manifests are published and read by different tools, so a disagreement +// between them decides which one the validator saw rather than which one is right. +test("both manifests name the same server", () => { + assert.equal(serverManifest.mcpName, packageManifest.mcpName); + assert.equal(serverManifest.version, packageManifest.version); +}); + +// A registry identifier is a primary key, so the npm scope is allowed to differ +// from it while the package name is not allowed to drift from it: `mcpName` +// ends in the unscoped package name by construction, and moving the package +// without moving the identifier would silently fork the registry record. +test("the registry name is the unscoped package name", () => { + const [, packageName] = packageManifest.name.split("/"); + assert.equal( + serverManifest.mcpName.split("/").pop(), + packageName, + "the trailing segment of mcpName must be the published package name without its scope", + ); + assert.equal( + serverManifest.name, + packageName, + "server.json name must match the published package name without its scope", + ); +}); diff --git a/npm/server.json b/npm/server.json index f214f7a..aa3fd6e 100644 --- a/npm/server.json +++ b/npm/server.json @@ -1,5 +1,5 @@ { - "mcpName": "io.github.fullstack-ai-infra/mem-mcp", + "mcpName": "io.github.bytefolk/mem-mcp", "name": "mem-mcp", "version": "0.1.1", "description": "MCP server for mem — a portable, self-hosted memory plane for AI agents",