From 9ea9b9b28f433566e7c73355b48a67b087ebcc1f Mon Sep 17 00:00:00 2001 From: pc-gemini Date: Fri, 14 Aug 2026 01:21:33 +0900 Subject: [PATCH] release: align MCP Registry metadata with v0.4.345 --- .github/workflows/publish-mcp-registry.yml | 45 ++++++++++++++ .github/workflows/publish.yml | 4 +- .github/workflows/release.yml | 6 +- README.md | 22 ++++--- lhm.plugin.json | 4 +- package-lock.json | 4 +- package.json | 2 +- scripts/check-release-version.mjs | 70 ++++++++++++++++------ server.json | 18 +++++- test/marketplace.test.js | 14 ++++- 10 files changed, 147 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/publish-mcp-registry.yml diff --git a/.github/workflows/publish-mcp-registry.yml b/.github/workflows/publish-mcp-registry.yml new file mode 100644 index 0000000..5dfe631 --- /dev/null +++ b/.github/workflows/publish-mcp-registry.yml @@ -0,0 +1,45 @@ +name: Publish MCP Registry metadata + +on: + workflow_dispatch: + inputs: + ref: + description: Commit, branch, or mcp-v tag containing the reviewed server.json + required: true + default: main + type: string + registry_tag: + description: Registry release identifier, for example mcp-v0.4.345 + required: true + type: string + +permissions: + contents: read + id-token: write + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + fetch-depth: 0 + + - name: Verify MCP Registry metadata + run: node scripts/check-release-version.mjs --channel mcp --tag "${{ inputs.registry_tag }}" + + - name: Install MCP Registry publisher + shell: bash + run: | + set -euo pipefail + curl -fsSL \ + https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz \ + | tar xz mcp-publisher + ./mcp-publisher --help + + - name: Authenticate with GitHub OIDC + run: ./mcp-publisher login github-oidc + + - name: Publish server metadata + run: ./mcp-publisher publish diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d682aad..25c117b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: ref: - description: Existing release tag to recover, for example v0.4.179 + description: Existing CLI release tag to recover, for example cli-v0.4.181 required: true type: string @@ -27,7 +27,7 @@ jobs: registry-url: https://registry.npmjs.org cache: npm - run: npm ci - - run: node scripts/check-release-version.mjs --tag "${{ inputs.ref }}" + - run: node scripts/check-release-version.mjs --channel cli --tag "${{ inputs.ref }}" - run: npm run lint - run: npm test - run: npm run pack:dry-run diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 54244a3..ab5ffe2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,9 +2,9 @@ name: Create Release on: push: - # CLI releases only: a v tag publishes @xmemo/client to npm. + # CLI releases only: a cli-v tag publishes @xmemo/client to npm. tags: - - 'v*.*.*' + - 'cli-v*.*.*' permissions: contents: write @@ -30,7 +30,7 @@ jobs: run: npm ci - name: Verify tag and release metadata - run: node scripts/check-release-version.mjs --tag "${{ github.ref_name }}" + run: node scripts/check-release-version.mjs --channel cli --tag "${{ github.ref_name }}" - name: Run package gate run: | diff --git a/README.md b/README.md index 35a2591..8d9a84e 100644 --- a/README.md +++ b/README.md @@ -488,19 +488,23 @@ Normal releases are produced by GitHub Actions from the exact tagged commit, not from a mutable branch checkout or a developer workstation: ```text -develop → version sync → test → tag → GitHub Actions → npm publish --provenance +develop → CLI version sync → test → cli-v tag → GitHub Actions → npm publish --provenance ``` -Version-bearing files must stay synchronized: +The CLI package and hosted MCP service intentionally have separate version +streams: -- `package.json` -- `package-lock.json` -- `server.json` -- `lhm.plugin.json` +- CLI/npm version: `package.json`, `package-lock.json`, and the npm package + entry in `server.json`. +- Hosted MCP/Registry version: the top-level `server.json.version` and + `lhm.plugin.json`. This version follows the deployed XMemo service. -`node scripts/check-release-version.mjs --tag vX.Y.Z` verifies the tag and every -version-bearing file before publication. The separate npm publish workflow is -manual recovery only, so creating a GitHub Release cannot publish twice. +`node scripts/check-release-version.mjs` verifies both contracts. A +`cli-vX.Y.Z` tag must equal the CLI/npm version and publishes only npm. The +MCP Registry is published separately with the `Publish MCP Registry metadata` +workflow using `mcp-vX.Y.Z`, which must equal the hosted MCP/Registry version. +The separate npm publish workflow is manual recovery only, so creating a +GitHub Release cannot publish twice. ## Documentation and support diff --git a/lhm.plugin.json b/lhm.plugin.json index ab1044b..d17a722 100644 --- a/lhm.plugin.json +++ b/lhm.plugin.json @@ -1,12 +1,12 @@ { "identifier": "yonro-memory-os-cli", "name": "XMemo CLI", - "description": "User-owned memory for AI agents over MCP. Save, recall, update, explain, and govern durable context across clients, projects, and sessions.", + "description": "Shared, governed long-term memory for AI agents — durable context across tools and sessions, exposed via MCP and REST.", "author": "yonro", "authorUrl": "https://github.com/yonro", "homepage": "https://xmemo.dev/product/mcp", "icon": "https://raw.githubusercontent.com/yonro/memory-os-cli/main/plugins/xmemo/assets/logo.png", - "version": "0.4.180", + "version": "0.4.345", "category": "productivity", "connectionType": "hybrid", "cloudEndpoint": "https://xmemo.dev/mcp", diff --git a/package-lock.json b/package-lock.json index e6880a3..139a3c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@xmemo/client", - "version": "0.4.180", + "version": "0.4.181", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@xmemo/client", - "version": "0.4.180", + "version": "0.4.181", "license": "MIT", "bin": { "memory-os": "bin/memory-os.js", diff --git a/package.json b/package.json index 5eb646c..3c66445 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xmemo/client", - "version": "0.4.180", + "version": "0.4.181", "description": "Privacy-first CLI and MCP setup helper for XMemo.", "mcpName": "io.github.yonro/xmemo", "type": "module", diff --git a/scripts/check-release-version.mjs b/scripts/check-release-version.mjs index c75683b..c06fd53 100644 --- a/scripts/check-release-version.mjs +++ b/scripts/check-release-version.mjs @@ -1,42 +1,76 @@ import fs from 'node:fs/promises'; -const expectedTag = readTagArgument(process.argv.slice(2)); +const args = process.argv.slice(2); +const tag = readTagArgument(args); +const channel = readChannelArgument(args); const packageJson = await readJson('package.json'); const packageLock = await readJson('package-lock.json'); const server = await readJson('server.json'); const marketplace = await readJson('lhm.plugin.json'); -const expectedVersion = packageJson.version; +const npmPackage = server.packages?.find((item) => item.identifier === packageJson.name); -const versions = [ - ['package.json', packageJson.version], +const cliVersion = packageJson.version; +const registryVersion = server.version; +const cliMismatches = [ ['package-lock.json', packageLock.version], - ['package-lock.json packages[""]', packageLock.packages?.['']?.version], - ['server.json', server.version], - ['server.json npm package', server.packages?.find((item) => item.identifier === packageJson.name)?.version], + ['package-lock.json packages[""].version', packageLock.packages?.['']?.version], + ['server.json npm package', npmPackage?.version] +].filter(([, version]) => version !== cliVersion); +const registryMismatches = [ ['lhm.plugin.json', marketplace.version] +].filter(([, version]) => version !== registryVersion); + +const activeChecks = [ + ...(channel !== 'mcp' ? [['CLI/npm', cliVersion, cliMismatches]] : []), + ...(channel !== 'cli' ? [['MCP Registry', registryVersion, registryMismatches]] : []) ]; -const mismatches = versions.filter(([, version]) => version !== expectedVersion); -if (mismatches.length > 0) { - for (const [file, version] of mismatches) { - console.error(`${file}: expected ${expectedVersion}, found ${version ?? 'missing'}`); +if (activeChecks.some(([, , mismatches]) => mismatches.length)) { + for (const [scope, expectedVersion, mismatches] of activeChecks) { + reportMismatches(scope, expectedVersion, mismatches); } process.exitCode = 1; -} else if (expectedTag && expectedTag !== `v${expectedVersion}`) { - console.error(`Release tag mismatch: expected v${expectedVersion}, found ${expectedTag}`); +} else if (tag && !matchesReleaseTag(tag, channel, cliVersion, registryVersion)) { + console.error( + `Release tag mismatch: expected cli-v${cliVersion} for npm or mcp-v${registryVersion} for MCP Registry, found ${tag}` + ); process.exitCode = 1; } else { - console.log(`Release metadata is synchronized at ${expectedVersion}${expectedTag ? ` (${expectedTag})` : ''}.`); + if (channel !== 'mcp') console.log(`CLI/npm metadata is synchronized at ${cliVersion}.`); + if (channel !== 'cli') console.log(`MCP Registry metadata is synchronized at ${registryVersion}.`); +} + +function matchesReleaseTag(value, selectedChannel, cli, registry) { + if (selectedChannel === 'cli') return value === `cli-v${cli}`; + if (selectedChannel === 'mcp') return value === `mcp-v${registry}`; + return value === `cli-v${cli}` || value === `mcp-v${registry}`; +} + +function reportMismatches(scope, expectedVersion, mismatches) { + for (const [file, version] of mismatches) { + console.error(`${scope} ${file}: expected ${expectedVersion}, found ${version ?? 'missing'}`); + } } function readTagArgument(args) { const index = args.indexOf('--tag'); - if (index === -1) { - return null; - } + if (index === -1) return null; + const value = args[index + 1]; if (!value) { - console.error('--tag requires a value such as v0.4.179'); + console.error('--tag requires a value such as cli-v0.4.181 or mcp-v0.4.345'); + process.exit(2); + } + return value; +} + +function readChannelArgument(args) { + const index = args.indexOf('--channel'); + if (index === -1) return null; + + const value = args[index + 1]; + if (value !== 'cli' && value !== 'mcp') { + console.error('--channel requires cli or mcp'); process.exit(2); } return value; diff --git a/server.json b/server.json index 38892e9..ff963c9 100644 --- a/server.json +++ b/server.json @@ -2,13 +2,13 @@ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "io.github.yonro/xmemo", "title": "XMemo", - "description": "User-owned memory for AI agents, Copilot, Claude, IDEs, CLIs, and chat apps over remote MCP.", + "description": "Shared, governed long-term memory for AI agents — durable context across tools and sessions, exposed via MCP and REST.", "websiteUrl": "https://xmemo.dev/product/mcp", "repository": { "url": "https://github.com/yonro/memory-os-cli", "source": "github" }, - "version": "0.4.180", + "version": "0.4.345", "remotes": [ { "type": "streamable-http", @@ -31,6 +31,18 @@ "description": "Optional stable local installation id for attribution; XMemo hashes it server-side.", "isRequired": false, "isSecret": false + }, + { + "name": "X-Memory-OS-Device-ID", + "description": "Optional stable local device id for attribution; use a non-secret machine or installation identifier.", + "isRequired": false, + "isSecret": false + }, + { + "name": "X-Memory-OS-Device-Label", + "description": "Optional human-readable local device label for attribution, for example 'MacBook Pro' or 'PC A'.", + "isRequired": false, + "isSecret": false } ] } @@ -39,7 +51,7 @@ { "registryType": "npm", "identifier": "@xmemo/client", - "version": "0.4.180", + "version": "0.4.181", "runtimeHint": "npx", "transport": { "type": "stdio" diff --git a/test/marketplace.test.js b/test/marketplace.test.js index 7894558..311d30d 100644 --- a/test/marketplace.test.js +++ b/test/marketplace.test.js @@ -20,10 +20,20 @@ test('LobeHub manifest uses the dedicated MCP server and mirrors its catalog', a readJson('server.json') ]); - assert.equal(manifest.version, packageJson.version); - assert.equal(serverJson.version, packageJson.version); + assert.equal(manifest.version, serverJson.version); assert.equal(serverJson.packages[0].version, packageJson.version); assert.equal(packageJson.bin['xmemo-mcp'], 'bin/mcp-stdio.js'); + assert.equal(manifest.description, serverJson.description); + assert.deepEqual( + serverJson.remotes[0].headers.map((header) => header.name), + [ + 'Authorization', + 'X-Memory-OS-Agent-ID', + 'X-Memory-OS-Agent-Instance-ID', + 'X-Memory-OS-Device-ID', + 'X-Memory-OS-Device-Label' + ] + ); const npmDeployment = manifest.deploymentOptions.find( (option) => option.installationMethod === 'npm'