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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- run: pnpm install --frozen-lockfile
- name: Verify package versions match pinned server
run: pnpm run check-versions
- name: Verify spec matches upstream pinned tag
- name: Verify spec matches upstream pinned source
run: pnpm run check-drift
- name: Verify generated SDK is up to date
run: |
Expand Down
55 changes: 51 additions & 4 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand All @@ -18,14 +19,17 @@ jobs:

- run: pnpm install --frozen-lockfile

- name: Read pinned server version
- name: Resolve pinned server source
id: ver
run: |
v=$(node -p "require('./package.json').omnigraph.serverVersion")
echo "version=$v" >> "$GITHUB_OUTPUT"
echo "Pinned omnigraph-server: v$v"
pnpm exec tsx -e '
import { readServerPin } from "./scripts/server-pin.ts";
const pin = readServerPin();
console.log("version=" + pin.version + "\nref=" + pin.ref + "\nsource_pinned=" + pin.sourcePinned);
' >> "$GITHUB_OUTPUT"

- name: Download omnigraph-server binary
if: steps.ver.outputs.source_pinned == 'false'
run: |
set -euo pipefail
v="${{ steps.ver.outputs.version }}"
Expand All @@ -42,6 +46,44 @@ jobs:
chmod +x "$HOME/.local/bin/omnigraph" "$HOME/.local/bin/omnigraph-server"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

# Until the server release exists, run the same e2e suite against the
# exact source used by sync-spec and the MCP references, never main.
- name: Check out immutable server candidate
if: steps.ver.outputs.source_pinned == 'true'
uses: actions/checkout@v4
with:
repository: ModernRelay/omnigraph
ref: ${{ steps.ver.outputs.ref }}
path: .server-source
persist-credentials: false

- name: Prepare server source build
if: steps.ver.outputs.source_pinned == 'true'
working-directory: .server-source
env:
SERVER_REF: ${{ steps.ver.outputs.ref }}
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$SERVER_REF"
sudo apt-get update
sudo apt-get install -y protobuf-compiler libprotobuf-dev
rustup toolchain install

- name: Cache server source build
if: steps.ver.outputs.source_pinned == 'true'
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: .server-source -> target

- name: Build pinned server candidate
if: steps.ver.outputs.source_pinned == 'true'
working-directory: .server-source
run: |
set -euo pipefail
cargo build --locked -p omnigraph-cli --bin omnigraph \
-p omnigraph-server --bin omnigraph-server
echo "$PWD/target/debug" >> "$GITHUB_PATH"

- name: Verify server binary version matches pin
run: |
set -euo pipefail
Expand All @@ -64,6 +106,8 @@ jobs:
dir=/tmp/cluster
mkdir -p "$dir"
cp packages/sdk/test/fixtures/schema.pg "$dir/graph.pg"
cp packages/sdk/test/fixtures/queries.gq "$dir/queries.gq"
omnigraph lint --schema "$dir/graph.pg" --query "$dir/queries.gq"

cat > "$dir/server.policy.yaml" <<'YAML'
version: 1
Expand Down Expand Up @@ -97,8 +141,10 @@ jobs:
graphs:
alpha:
schema: ./graph.pg
queries: [./queries.gq]
beta:
schema: ./graph.pg
queries: [./queries.gq]
policies:
server:
file: ./server.policy.yaml
Expand All @@ -109,6 +155,7 @@ jobs:
YAML

omnigraph cluster import --config "$dir"
omnigraph cluster plan --config "$dir"
omnigraph cluster apply --config "$dir"

- name: Seed fixture data into each graph
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ jobs:

# Gates — same bar as ci.yml, run again on the exact tag SHA so a stale
# CI run cannot bless a release.
- run: pnpm run check-versions
- run: pnpm run check-drift
# Reject source-pinned candidates, including manual package publishes
# (the same gate is also each package's prepublishOnly hook).
- run: pnpm run check-release
- run: pnpm run check-coverage
- run: pnpm run build
- run: pnpm run typecheck
Expand Down
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ TypeScript packages for the [Omnigraph](https://github.com/ModernRelay/omnigraph

```
.
├── spec/openapi.json # committed copy of upstream OpenAPI at the pinned tag
├── spec/openapi.json # committed copy of upstream OpenAPI at the pinned source
├── scripts/ # spec sync, drift check, version-stamp generator
├── packages/
│ ├── sdk/ # @modernrelay/omnigraph
Expand All @@ -23,9 +23,11 @@ TypeScript packages for the [Omnigraph](https://github.com/ModernRelay/omnigraph

## Server-version pin

The SDK is built against a specific `omnigraph-server` release. The pin lives in **`package.json#omnigraph.serverVersion`** at the repo root and is the single source of truth — `scripts/sync-spec.ts` reads it to fetch the matching OpenAPI spec, and `scripts/gen-version.ts` writes it into `packages/sdk/src/version.gen.ts` so consumers can `import { SERVER_VERSION } from '@modernrelay/omnigraph'`.
The SDK targets the `omnigraph-server` version in **`package.json#omnigraph.serverVersion`**. By default, the source is the matching `vX.Y.Z` tag. Before a server release exists, **`omnigraph.serverRef`** may temporarily pin a full immutable commit SHA. The OpenAPI spec, MCP reference documents, and live CI server all use that same source. Branch names and abbreviated SHAs are rejected.

CI enforces drift in two ways: a structural check that the bundled spec matches the upstream tag exactly, and an end-to-end suite that runs against a live `omnigraph-server` of the pinned release.
The **v0.10** line targets `omnigraph-server` **v0.10.0**. Upgrade the CLI, server, and client integrations together; see the [v0.9 migration notes](packages/sdk/README.md#migrating-from-v09). Development source pins remain supported, but block publishing through both the release workflow and each package's `prepublishOnly` hook.

`scripts/gen-version.ts` stamps the target version as `SERVER_VERSION`. CI checks that the bundled spec matches the pinned source byte for byte and runs live e2e tests against it: a checksum-verified release binary for tags, or a source build for commit pins.

### Versioning policy

Expand All @@ -35,16 +37,17 @@ In practice: server cuts `0.4.2`, SDK ships `0.4.0` (and `0.4.1`, `0.4.2`, … a

## Workflow when omnigraph cuts a new release

1. Bump `package.json#omnigraph.serverVersion` to the new tag (e.g., `0.4.0`).
1. Bump `package.json#omnigraph.serverVersion` to the new tag (e.g., `0.10.0`). If upgrading from a source-pinned candidate, remove `omnigraph.serverRef` after the release tag exists.
2. `pnpm run sync-spec` — fetches the matching `openapi.json` into `spec/`.
3. `pnpm run generate` — regenerates `packages/sdk/src/generated/` and `packages/sdk/src/version.gen.ts`.
4. Commit `spec/openapi.json`, `packages/sdk/src/generated/`, `packages/sdk/src/version.gen.ts`, and the bumped `package.json`. The PR shows the full upstream change.
5. Bump `packages/sdk/package.json#version` (and `packages/mcp/package.json#version`) to match.
6. Tag `vX.Y.Z`. `release.yml` publishes both packages to npm (see [Releasing](#releasing)).
6. Run the full checks, including `pnpm run check-release`, against the released server; the final tag may differ from an earlier source-pinned candidate.
7. Tag `vX.Y.Z`. `release.yml` publishes both packages to npm (see [Releasing](#releasing)).

## Releasing

Pushing a `v*` tag triggers `.github/workflows/release.yml`, which runs the full gate (drift, coverage, build, typecheck, test) on the tag SHA and then publishes both `@modernrelay/omnigraph` and `@modernrelay/omnigraph-mcp` with npm provenance. The dist-tag is derived from the tag name: `v1.2.3-alpha.1`, `-beta.x`, `-rc.x` ship under `next`; everything else under `latest`.
Pushing a `v*` tag triggers `.github/workflows/release.yml`, which runs the full gate (release pin, drift, coverage, build, typecheck, test) on the tag SHA and then publishes both `@modernrelay/omnigraph` and `@modernrelay/omnigraph-mcp` with npm provenance. `check-release` rejects a source pin and verifies the spec against the released server tag. The dist-tag is derived from the tag name: `v1.2.3-alpha.1`, `-beta.x`, `-rc.x` ship under `next`; everything else under `latest`.

```sh
pnpm --filter @modernrelay/omnigraph version 0.4.0-alpha.1
Expand All @@ -61,12 +64,13 @@ One-time setup: add an npm `NPM_TOKEN` repo secret (use an Automation token to b

```sh
pnpm install
pnpm run check-drift # asserts spec matches the pinned server tag
pnpm run check-drift # asserts spec matches the pinned tag or immutable commit
pnpm run generate # regenerates types + version stamp
pnpm run check-coverage # asserts every spec op has an SDK binding
pnpm run build # builds all workspace packages (SDK first, then MCP)
pnpm run typecheck # runs after build so workspace types resolve
pnpm run test # mocked unit tests across all packages
pnpm run test # pin-tooling tests + mocked unit tests across all packages
pnpm run check-release # release-only gate; intentionally fails with serverRef set

# Live e2e against a real cluster server (0.7.0 is cluster-only — boot it with
# `omnigraph-server --cluster <dir>`; see packages/sdk/test/e2e.test.ts header
Expand All @@ -75,7 +79,7 @@ OMNIGRAPH_E2E=1 OMNIGRAPH_BASE_URL=http://127.0.0.1:8080 OMNIGRAPH_TOKEN=$TOKEN
OMNIGRAPH_GRAPH_ID=alpha pnpm --filter @modernrelay/omnigraph run test
```

CI runs the same sequence (see `.github/workflows/ci.yml` and `e2e.yml`) and downloads the pinned `omnigraph-server` release binary for the e2e job.
CI runs the same sequence (see `.github/workflows/ci.yml` and `e2e.yml`). Its e2e job downloads the pinned release binary or builds the immutable source candidate, then runs the same cluster bootstrap and tests in either mode.

## License

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@
},
"packageManager": "pnpm@9.15.0",
"omnigraph": {
"serverVersion": "0.9.0"
"serverVersion": "0.10.0"
},
"scripts": {
"sync-spec": "tsx scripts/sync-spec.ts",
"check-drift": "tsx scripts/check-drift.ts",
"check-versions": "tsx scripts/check-versions.ts",
"check-release": "tsx scripts/check-versions.ts --release && tsx scripts/check-drift.ts",
"gen-version": "tsx scripts/gen-version.ts",
"generate": "tsx scripts/gen-version.ts && pnpm --filter @modernrelay/omnigraph run generate",
"build": "pnpm -r run build",
"typecheck": "pnpm -r run typecheck",
"test": "pnpm -r run test",
"test": "pnpm run test:tooling && pnpm -r run test",
"test:tooling": "tsx --test scripts/*.test.ts",
"check-coverage": "tsx scripts/check-coverage.ts"
},
"devDependencies": {
Expand Down
49 changes: 45 additions & 4 deletions packages/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,31 +55,72 @@ Read-only (`readOnlyHint: true`):
| Tool | Purpose |
|---|---|
| `health` | Server liveness + version |
| `snapshot` | Snapshot of a branch (table list + row counts) |
| `query` | Run a `.gq` read query |
| `snapshot` | Snapshot of a branch (`datasets`, type names, entity counts, graph manifest version) |
| `query` | Run a `.gq` read query; returns the exact read's `graphCommitId` |
| `schema_get` | Active `.pg` schema source |
| `branches_list` | List user-visible branches |
| `commits_list` | List commits on a branch |
| `commits_get` | Retrieve a single commit |
| `commits_changes` | One bounded page of a commit's entity changes relative to its first parent |
| `changes_poll` | One bounded page of the branch's at-least-once change feed |
| `graphs_list` | List registered graphs in the cluster (requires a `graph_list` policy grant) |

Mutating (`destructiveHint: true` where appropriate — hosts should surface confirmation):

| Tool | Purpose |
|---|---|
| `mutate` | Run a `.gq` mutation |
| `load` | Bulk-load NDJSON (`mode: 'merge'` for idempotency). Without `from`, a missing branch is a 404. |
| `mutate` | Run a `.gq` mutation; optional `ifGraphCommit` selects the conditional-write route |
| `load` | Bulk-load NDJSON (`mode: 'merge'` for upserts). Without `from`, a missing branch is a 404. |
| `branches_create` | Create a new branch |
| `branches_delete` | Delete a branch |
| `branches_merge` | Merge `source` into `target` |

There is **no `schema_apply` tool**: a cluster-managed graph rejects HTTP schema apply (409). Schema is read-only here (`schema_get`); evolve it via `omnigraph cluster apply`.

### v0.10 writes, errors, and change pages

Successful `mutate` and `load` results include the exact `commit` receipt from
publication. `mutate` returning `commit: null` means a successful no-op. Comparing
branch heads before and after is not a receipt: another writer may advance the
head, and a timed-out operation may still be running.

For read-modify-write, pass `query`'s `graphCommitId` as `mutate`'s
`ifGraphCommit` argument. The SDK uses `/mutate/if-graph-commit`; it never silently
falls back to an unconditional write. HTTP 412 with `body.preconditionFailure`
means no effects: re-read and reconsider the mutation.

SDK request failures set `isError: true` and return JSON with `error`, `status`, `code`,
`requestId` when available, and the structured server `body`. Request headers and
request/response objects are not included. No request is retried automatically.
A 409 is not one universal retry condition: `fullTextIndexRebuildRequired` needs
an operator's branch-scoped `rebuild-full-text-indexes`, `keyConflict` needs an
identity/operation decision, and merge conflicts need reconciliation.
`recoveryRequired` needs operator recovery. A lost response leaves the outcome
unknown; inspect intended content and relevant history before deciding to replay.

`commits_changes` accepts `commitId`, optional `pageToken`, `limit`, and array
filters `kind`, `type`, and `op`. `changes_poll` accepts the same filters plus
`branch` and exactly one of `start`, `cursor`, or `pageToken` (or none, meaning
`start: "now"`). Follow `nextPageToken` with unchanged branch/filters. Only a
terminal feed `cursor` is durable; apply complete commit blocks idempotently by
`graphCommitId` and persist that cursor with the applied data. A 410
`changeFeedGap` requires the SDK's streamed baseline/reset workflow. Full baseline
exports and binary Blob payloads are deliberately not buffered into MCP results.

### Resources

- `omnigraph://schema` — text/plain `.pg` source
- `omnigraph://branches` — application/json branch name list
- `omnigraph://graphs` — application/json `[{ graphId, uri }]` (requires a `graph_list` policy grant; the management surface is closed by default)
- `omnigraph://best-practices/index` — index of the bundled query, data, schema, and search references

Best-practice references are fetched from the same pinned upstream contract as
the SDK, not moving `main`. Operator/CLI examples are not additional MCP tools;
available types, properties, and vector dimensions come from the live schema.
The older `remote-ops` reference is intentionally excluded until its blanket
retry and branch-head verification advice is refreshed for v0.10. The write/error
rules above and the server's initialization instructions are authoritative for
this MCP version.

## License

Expand Down
9 changes: 3 additions & 6 deletions packages/mcp/cookbook-descriptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,15 @@
},
"schema": {
"title": "Schema authoring and evolution",
"description": "Read before calling `schema_apply`. Covers .pg grammar, decorators (@key, @unique, @embed, @rename_from), inline-only enums, the add-optional→backfill→tighten sequence for non-nullable additions, and why apply is main-only and rejects open feature branches."
},
"remote-ops": {
"title": "Remote-operation safety",
"description": "Read after any 504 or unexpected error. Covers the verify-after-write ritual (commits_list head before/after), retry-safety table by node kind (pointer types dedupe via @key; append-only types duplicate on retry), and the `sync_branch()` server-internal error vs. a tool."
"description": "Read to interpret `schema_get` and .pg declarations. Covers decorators, inline enums, stable renames, and add-optional/backfill evolution; tightening optional properties remains unsupported. Schema changes are operator-owned through cluster apply, not an MCP tool."
},
"search": {
"title": "Vector and full-text search",
"description": "Read before using nearest/bm25/rrf. Covers the scope-first-rank-second pattern, the hardcoded embedding model (gemini-embedding-2-preview, Vector(3072)), and trailing-`limit` requirement."
"description": "Read before using nearest/bm25/rrf. Covers scope-first-rank-second queries, schema-declared embeddings, and the trailing-`limit` requirement. Match vector dimensions and models to the live deployment; CLI examples are not MCP tools."
}
},
"skipped": {
"remote-ops": "Awaiting upstream v0.10 refresh: its blanket 409 retry and branch-head verification advice conflicts with exact commit receipts and typed refusals. Current MCP instructions and README define write/error handling instead.",
"aliases": "CLI-only — operator aliases + --alias mechanics, not actionable through the HTTP API the MCP wraps.",
"commands": "CLI-only — the omnigraph binary's command surface, not the HTTP API.",
"server-policy": "Server-deployment concerns (Cedar policy, auth tokens), out of scope for an MCP client.",
Expand Down
4 changes: 2 additions & 2 deletions packages/mcp/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@modernrelay/omnigraph-mcp",
"version": "0.9.0",
"version": "0.10.0",
"description": "MCP server exposing an Omnigraph database to LLM clients (Tools + Resources, stdio transport).",
"license": "MIT",
"repository": {
Expand Down Expand Up @@ -39,7 +39,7 @@
"pretest": "pnpm run sync-cookbook",
"test": "vitest run",
"clean": "rm -rf dist src/best-practices.gen.ts",
"prepublishOnly": "pnpm run build"
"prepublishOnly": "pnpm --dir ../.. run check-release && pnpm run build"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
Expand Down
6 changes: 4 additions & 2 deletions packages/mcp/scripts/sync-cookbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,24 @@
// the cue it needs.
//
// Source of truth: the `omnigraph` skill references in ModernRelay/omnigraph @
// main (moved there from the retired ModernRelay/omnigraph-cookbooks repo). The
// the same server pin as the OpenAPI spec (release tag or immutable candidate).
// Moved there from the retired ModernRelay/omnigraph-cookbooks repo. The
// generated TS module is gitignored; every build/typecheck regenerates it. CI
// builds always fetch fresh; the published npm tarball ships the bundled JS with
// the markdown inlined as string constants.

import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { readServerPin } from '../../../scripts/server-pin.js';

const HERE = dirname(fileURLToPath(import.meta.url));
const PKG_ROOT = dirname(HERE);
const OUT = join(PKG_ROOT, 'src/best-practices.gen.ts');
const DESCRIPTIONS_PATH = join(PKG_ROOT, 'cookbook-descriptions.json');

const REPO = 'ModernRelay/omnigraph';
const REF = 'main';
const REF = readServerPin().ref;
const REF_DIR = 'skills/omnigraph/references';

const LIST_URL = `https://api.github.com/repos/${REPO}/contents/${REF_DIR}?ref=${REF}`;
Expand Down
Loading
Loading