Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions .github/workflows/mcp-registry-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: MCP Registry Publish

# G5: official MCP Registry metadata. Does not host the binary.
# Requires G4: @bytefolk/mem-mcp must already be on npm with matching mcpName.
# Auth is GitHub OIDC against io.github.bytefolk/* — not a personal device login.
# Founder gate: environment mcp-registry (same owner as npm-release).

on:
workflow_dispatch:
inputs:
version:
description: "Published npm/package version (e.g. 0.1.2, no v prefix)"
required: true
type: string

permissions:
contents: read

concurrency:
group: mcp-registry-publish-bytefolk-mem-mcp
cancel-in-progress: false

jobs:
publish:
name: Publish io.github.bytefolk/mem-mcp (OIDC)
if: github.repository == 'bytefolk/mem'
runs-on: ubuntu-24.04
timeout-minutes: 10
environment: mcp-registry
permissions:
contents: read
id-token: write
steps:
- name: Check out default branch for registry manifest
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Require @bytefolk/mem-mcp on npm before Registry write
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if [[ ! "${VERSION}" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo "expected X.Y.Z, got ${VERSION}" >&2
exit 1
fi
meta="$(curl -fsS "https://registry.npmjs.org/@bytefolk/mem-mcp/${VERSION}")"
name="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["name"])' <<<"${meta}")"
mcp="$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("mcpName",""))' <<<"${meta}")"
[[ "${name}" == "@bytefolk/mem-mcp" ]]
[[ "${mcp}" == "io.github.bytefolk/mem-mcp" ]]

- name: Install mcp-publisher
env:
MCP_PUBLISHER_VERSION: v1.8.1
MCP_PUBLISHER_LINUX_AMD64_SHA256: a06c9096dcb9727c13555b6be26c7effa707b01f06a4c561ba7a3635443cf2cc
MCP_PUBLISHER_LINUX_ARM64_SHA256: 8dd75a6cf6845688b5d4e46df58d3ca26d5c8d233bb0626606e1db82c5e883e4
run: |
set -euo pipefail
os="$(uname -s | tr '[:upper:]' '[:lower:]')"
arch="$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')"
checksum_var="MCP_PUBLISHER_$(printf '%s_%s' "${os}" "${arch}" | tr '[:lower:]' '[:upper:]')_SHA256"
checksum="${!checksum_var:-}"
[[ "${checksum}" =~ ^[0-9a-f]{64}$ ]] || {
echo "no pinned mcp-publisher checksum for ${os}/${arch}" >&2
exit 1
}
archive="${RUNNER_TEMP}/mcp-publisher_${os}_${arch}.tar.gz"
curl -fsSL --retry 3 \
"https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/mcp-publisher_${os}_${arch}.tar.gz" \
--output "${archive}"
printf '%s %s\n' "${checksum}" "${archive}" | sha256sum --check --strict
tar -xzf "${archive}" mcp-publisher
chmod +x ./mcp-publisher

- name: Stage official server.json
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
python3 - <<'PY'
import json, os
path = "npm/mcp-registry.server.json"
with open(path) as f:
doc = json.load(f)
version = os.environ["VERSION"]
doc["version"] = version
doc["packages"][0]["version"] = version
with open("server.json", "w") as f:
json.dump(doc, f, indent=2)
f.write("\n")
PY

- name: Login with GitHub OIDC and publish
run: |
set -euo pipefail
./mcp-publisher login github-oidc
./mcp-publisher publish

- name: Read back Registry search
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
curl -fsS "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.bytefolk/mem-mcp" \
| tee "${RUNNER_TEMP}/mcp-registry-readback.json"
python3 - <<'PY'
import json, os, sys
data = json.load(open(os.environ["RUNNER_TEMP"] + "/mcp-registry-readback.json"))
servers = data.get("servers") or data.get("result") or []
if not servers:
sys.exit("HOLD: registry search returned no servers")
print("G5 readback: %s" % json.dumps(servers[0])[:2000])
PY
95 changes: 68 additions & 27 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -1,47 +1,54 @@
name: NPM Publish

# G4 (npm publish) lives in its own workflow because scripts/test_release_guards.sh
# forbids `npm publish` inside release.yml and requires the draft publication to
# remain release.yml's final command. G1 (GitHub Release) must exist first:
# install.js pulls the platform binaries from the Release at install time.

# GitHub binary publication stays in release.yml. A Release created with the
# repository GITHUB_TOKEN may not trigger this workflow; dispatch the exact tag
# explicitly after G1 asset verification (see docs/maintainers/releasing.md).
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: "Existing published release tag to publish to npm (e.g., v0.1.0)"
description: "Existing stable tag, also selected as the workflow ref (v0.1.2)"
required: true
type: string

permissions:
contents: read
id-token: write

# Serialize every version of this package: next is shared across releases.
concurrency:
group: npm-publish-${{ inputs.version || github.event.release.tag_name }}
group: npm-publish-bytefolk-mem-mcp
cancel-in-progress: false

jobs:
npm-publish:
name: Publish npm package (OIDC Trusted Publishing)
name: Publish verified npm tarball to next (OIDC)
if: github.repository == 'bytefolk/mem'
runs-on: ubuntu-24.04
timeout-minutes: 10
timeout-minutes: 20
environment: npm-release
permissions:
contents: read
id-token: write
env:
NPM_RELEASE_PROOF: ${{ vars.NPM_RELEASE_PROOF }}
steps:
- name: Resolve release tag
- name: Validate exact stable event and tag before checkout
id: tag
env:
INPUT_VERSION: ${{ inputs.version }}
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail
TAG="${INPUT_VERSION:-${RELEASE_TAG_NAME}}"
if [[ ! "${TAG}" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo "expected a release tag such as v0.1.0, got: ${TAG:-<empty>}" >&2
tag="${INPUT_VERSION:-${RELEASE_TAG_NAME}}"
if [[ ! "${tag}" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo 'HOLD: an exact stable vX.Y.Z tag is required' >&2
exit 1
fi
printf 'tag=%s\n' "${TAG}" >> "${GITHUB_OUTPUT}"
[[ "${GITHUB_REF}" == "refs/tags/${tag}" ]]
[[ "${GITHUB_EVENT_NAME}" == release || "${GITHUB_EVENT_NAME}" == workflow_dispatch ]]
printf 'tag=%s\n' "${tag}" >> "${GITHUB_OUTPUT}"

- name: Check out exact tag commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -50,22 +57,56 @@ jobs:
fetch-depth: 0
persist-credentials: false

- name: Require the GitHub Release (G1) to exist before npm publish (G4)
- name: Set up Node 24 without release caches
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: '24'
package-manager-cache: false

- name: Require current release-owner org and publisher proof
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.tag.outputs.tag }}
run: |
node --input-type=module -e '
import { checkProof } from "./scripts/npm-release.mjs";
checkProof(JSON.parse(process.env.NPM_RELEASE_PROOF || "null"),
process.env.RELEASE_TAG, process.env.GITHUB_SHA);
'

- name: Install reviewed npm CLI with lifecycle scripts disabled
run: |
set -euo pipefail
gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null
node -e '
const fs = require("node:fs"), p = process.env.RUNNER_TEMP;
fs.writeFileSync(p + "/bootstrap-user.npmrc", "", {flag: "wx", mode: 0o600});
fs.writeFileSync(p + "/bootstrap-global.npmrc", "", {flag: "wx", mode: 0o600});
'
NPM_CONFIG_USERCONFIG="${RUNNER_TEMP}/bootstrap-user.npmrc" \
NPM_CONFIG_GLOBALCONFIG="${RUNNER_TEMP}/bootstrap-global.npmrc" \
NPM_CONFIG_CACHE="${RUNNER_TEMP}/bootstrap-npm-cache" \
npm install --global npm@11.15.0 --ignore-scripts --registry=https://registry.npmjs.org

- name: Set up Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- name: Test release refusal paths and npm wrapper
run: |
node --test scripts/npm-release.test.mjs
npm test --prefix npm

- name: Set up Go for read-only binary metadata inspection
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
node-version: 22
go-version-file: server/go.mod
cache: false

- name: Preflight, publish next with provenance, verify registry and signatures
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.tag.outputs.tag }}
run: node scripts/npm-release.mjs "${RELEASE_TAG}"

- name: Publish @fullstack-ai-infra/mem-mcp (OIDC)
working-directory: npm
# No NPM_TOKEN / NODE_AUTH_TOKEN: auth is exchanged from the GitHub OIDC
# id-token against the npmjs Trusted Publisher configured for
# org=fullstack-ai-infra / repo=mem / workflow=npm-publish.yml.
run: npm publish --provenance --access public
- name: Record next receipt and separate owner gates
run: |
node <<'NODE'
const fs = require("node:fs");
const receipt = fs.readFileSync(process.env.RUNNER_TEMP + "/mem-npm-release/receipt.json", "utf8");
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, "Published to next; latest requires release-owner acceptance.\n\n```json\n" + receipt + "\n```\n");
NODE
28 changes: 16 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ The project publishes 0.x prerelease versions; a stable release line is not yet

- Ingest cursor locks try non-blocking exclusive locks and give up after 5s so a wedged peer becomes a warning instead of a silent hang. Refs #139.

## [0.1.2] - 2026-09-18

### Added

- Additive `durable-memory.v1` envelope for derived RoleWeave/mem records
Expand Down Expand Up @@ -79,17 +81,18 @@ The project publishes 0.x prerelease versions; a stable release line is not yet
platform-equivalence table covering macOS, Ubuntu/Debian and WSL2 (`#109`).
`mem doctor` already names `deploy/compose` on a machine with no config.
- Migrate GitHub repository, Release, issue, badge, and raw-content coordinates
to the canonical `bytefolk` organization while retaining the published npm
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.
to the canonical `bytefolk` organization.
- Rename the npm wrapper to `@bytefolk/mem-mcp@0.1.2` and the MCP registry
identity to `io.github.bytefolk/mem-mcp`. New executable caches use
`bytefolk/mem-mcp`; a matching version/platform in the old
`fullstack-ai-infra/mem-mcp` cache can seed a separately verified copy.
Old cache entries, including 0.1.1, are never changed or removed by this
compatibility lookup. Explicit cache overrides keep their existing meaning.
The old npm package remains available for rollback; migration does not
unpublish it or change stored memories. Update host package arguments using
the migration guide in `npm/README.md`.
`npm/registry-identity.test.js` asserts the identifier against the
repository coordinate the installer itself uses.
- Internal: the local ingestion mechanics used by
`mem ingest qoder` — deterministic recursive transcript walk, per-path line
cursors (atomic rename write, reset when a file is rewritten shorter), the
Expand Down Expand Up @@ -567,6 +570,7 @@ The project publishes 0.x prerelease versions; a stable release line is not yet
- Preserve the primary Web acceptance failure when browser or Vite cleanup
also fails.

[Unreleased]: https://github.com/bytefolk/mem/compare/v0.1.1...HEAD
[Unreleased]: https://github.com/bytefolk/mem/compare/v0.1.2...HEAD
[0.1.2]: https://github.com/bytefolk/mem/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/bytefolk/mem/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/bytefolk/mem/releases/tag/v0.1.0
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![Status](https://img.shields.io/badge/status-experimental-orange.svg)](#project-status)
[![MCP Server](https://img.shields.io/badge/MCP%20Server-26%20tools-blue?logo=modelcontextprotocol)](docs/mcp.md)
[![smithery](https://smithery.ai/badge/@fullstack-ai-infra/mem-mcp)](https://smithery.ai/server/@fullstack-ai-infra/mem-mcp)
[![smithery](https://smithery.ai/badge/@bytefolk/mem-mcp)](https://smithery.ai/server/@bytefolk/mem-mcp)

**A portable, self-hosted memory plane for AI agents.**

Expand Down
4 changes: 2 additions & 2 deletions deploy/helm/mem/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ apiVersion: v2
name: mem
description: Production Web, memd, migration, and Worker workloads for mem
type: application
version: 0.1.1
appVersion: "0.1.1"
version: 0.1.2
appVersion: "0.1.2"
kubeVersion: ">=1.28.0-0"
6 changes: 3 additions & 3 deletions deploy/helm/mem/values-production.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
images:
server:
repository: registry.example.internal/mem/server
tag: "0.1.1"
tag: "0.1.2"
worker:
repository: registry.example.internal/mem/worker
tag: "0.1.1"
tag: "0.1.2"
web:
repository: registry.example.internal/mem/web
tag: "0.1.1"
tag: "0.1.2"

existingSecret: mem-runtime

Expand Down
6 changes: 3 additions & 3 deletions deploy/helm/mem/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ runtime:
images:
server:
repository: mem-server
tag: "0.1.1"
tag: "0.1.2"
pullPolicy: IfNotPresent
worker:
repository: mem-worker
tag: "0.1.1"
tag: "0.1.2"
pullPolicy: IfNotPresent
web:
repository: mem-web
tag: "0.1.1"
tag: "0.1.2"
pullPolicy: IfNotPresent

serviceAccount:
Expand Down
2 changes: 1 addition & 1 deletion docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Use an immutable version for all three images. The example below builds the
model-free Worker; optional heavy extras must be explicitly selected.

```bash
export MEM_VERSION=0.1.1
export MEM_VERSION=0.1.2
export MEM_REVISION="$(git rev-parse HEAD)"
export MEM_REGISTRY=registry.example.internal/mem

Expand Down
Loading
Loading