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
220 changes: 61 additions & 159 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,210 +51,112 @@ jobs:
echo "version mismatch: pinned $v, got $got"; exit 1
fi

- name: Init repo and load fixture
# 0.7.0 is cluster-only: the server boots solely from `--cluster <dir>` and
# serves every graph under /graphs/{id}/…. Build a local-filesystem cluster
# (no object storage) declaring two graphs, alpha + beta, with Cedar policy
# bundles: a `cluster`-scoped bundle granting `graph_list` (so GET /graphs
# works) and a per-graph bundle granting the data-plane actions the SDK
# exercises. Auth is on (token → actor `default`) so the UnauthorizedError
# path is covered too.
- name: Build local cluster (alpha + beta) with policy
run: |
set -euo pipefail
mkdir -p /tmp/og
omnigraph init --schema packages/sdk/test/fixtures/schema.pg /tmp/og/repo.omni
omnigraph load --data packages/sdk/test/fixtures/data.jsonl --mode overwrite /tmp/og/repo.omni
dir=/tmp/cluster
mkdir -p "$dir"
cp packages/sdk/test/fixtures/schema.pg "$dir/graph.pg"

- name: Write Cedar policy + omnigraph.yaml (single-graph)
run: |
# v0.6 default-denies non-read actions when a token is configured but
# no policy is set. Authorize the implicit `default` actor (used by
# OMNIGRAPH_SERVER_BEARER_TOKEN) for every action this e2e exercises.
cat > /tmp/og/policy.yaml <<'YAML'
cat > "$dir/server.policy.yaml" <<'YAML'
version: 1
groups:
ci: [default]
rules:
- id: ci-all-actions
- id: ci-graph-list
allow:
actors: { group: ci }
actions:
- read
- export
- change
- schema_apply
- branch_create
- branch_delete
- branch_merge
YAML
cat > /tmp/og/omnigraph.yaml <<'YAML'
graphs:
e2e:
uri: /tmp/og/repo.omni
# Per-graph policy: server 0.6.1+ rejects a top-level `policy.file`
# alongside a named graph (it would be silently ignored), so the
# policy must live under the graph it applies to.
policy:
file: /tmp/og/policy.yaml
cli:
graph: e2e
branch: main
actions: [graph_list]
YAML

- name: Start omnigraph-server in background (single-graph)
run: |
set -euo pipefail
OMNIGRAPH_SERVER_BEARER_TOKEN=ci-token \
nohup omnigraph-server --target e2e --config /tmp/og/omnigraph.yaml --bind 127.0.0.1:18080 \
> /tmp/og/server.log 2>&1 &
echo $! > /tmp/og/server.pid
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:18080/healthz > /dev/null; then
echo "server up after ${i}s"; exit 0
fi
sleep 1
done
echo "server failed to start within 30s"; cat /tmp/og/server.log; exit 1

- name: Run single-graph e2e tests
env:
OMNIGRAPH_E2E: '1'
OMNIGRAPH_BASE_URL: http://127.0.0.1:18080
OMNIGRAPH_TOKEN: ci-token
run: pnpm --filter @modernrelay/omnigraph run test

- name: Stop server
if: always()
run: |
if [ -f /tmp/og/server.pid ]; then
kill "$(cat /tmp/og/server.pid)" || true
fi

- name: Upload server log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: omnigraph-server-log
path: /tmp/og/server.log

e2e-multigraph:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- run: pnpm install --frozen-lockfile

- name: Read pinned server version
id: ver
run: |
v=$(node -p "require('./package.json').omnigraph.serverVersion")
echo "version=$v" >> "$GITHUB_OUTPUT"

- name: Download omnigraph-server binary
run: |
set -euo pipefail
v="${{ steps.ver.outputs.version }}"
asset="omnigraph-linux-x86_64.tar.gz"
checksum="omnigraph-linux-x86_64.sha256"
mkdir -p "$HOME/.local/bin"
curl -fsSL -o "/tmp/${asset}" \
"https://github.com/ModernRelay/omnigraph/releases/download/v${v}/${asset}"
curl -fsSL -o "/tmp/${checksum}" \
"https://github.com/ModernRelay/omnigraph/releases/download/v${v}/${checksum}"
(cd /tmp && sha256sum -c "${checksum}")
tar -C "$HOME/.local/bin" -xzf "/tmp/${asset}"
chmod +x "$HOME/.local/bin/omnigraph" "$HOME/.local/bin/omnigraph-server"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

- name: Init two graphs (alpha, beta) from the same fixture
run: |
set -euo pipefail
mkdir -p /tmp/ogm
for g in alpha beta; do
omnigraph init --schema packages/sdk/test/fixtures/schema.pg "/tmp/ogm/${g}.omni"
omnigraph load --data packages/sdk/test/fixtures/data.jsonl --mode overwrite "/tmp/ogm/${g}.omni"
done

- name: Write server + per-graph policy files
run: |
# Server-scoped policy: authorize `graph_list` for the default actor.
# /graphs is closed by default in every state — even unauthenticated.
cat > /tmp/ogm/server-policy.yaml <<'YAML'
cat > "$dir/graph.policy.yaml" <<'YAML'
version: 1
groups:
ci: [default]
rules:
- id: ci-can-list-graphs
- id: ci-data-plane
allow:
actors: { group: ci }
actions: [graph_list]
actions: [read, export, change, schema_apply, branch_create, branch_delete, branch_merge, invoke_query]
YAML
# Per-graph policy for alpha: authorize every per-graph action the
# SDK e2e exercises. beta gets the same so `og.graph("beta")` works.
cat > /tmp/ogm/per-graph-policy.yaml <<'YAML'

cat > "$dir/cluster.yaml" <<'YAML'
version: 1
groups:
ci: [default]
rules:
- id: ci-all-actions
allow:
actors: { group: ci }
actions:
- read
- export
- change
- schema_apply
- branch_create
- branch_delete
- branch_merge
YAML
cat > /tmp/ogm/omnigraph.yaml <<'YAML'
server:
policy:
file: /tmp/ogm/server-policy.yaml
metadata:
name: e2e
state:
backend: cluster
lock: true
graphs:
alpha:
uri: /tmp/ogm/alpha.omni
policy:
file: /tmp/ogm/per-graph-policy.yaml
schema: ./graph.pg
beta:
uri: /tmp/ogm/beta.omni
policy:
file: /tmp/ogm/per-graph-policy.yaml
schema: ./graph.pg
policies:
server:
file: ./server.policy.yaml
applies_to: [cluster]
data:
file: ./graph.policy.yaml
applies_to: [alpha, beta]
YAML

- name: Start omnigraph-server in multi-graph mode
omnigraph cluster import --config "$dir"
omnigraph cluster apply --config "$dir"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass an actor to cluster apply

In this fresh Actions runner, nothing creates an operator config with operator.actor before the new cluster setup runs. The 0.7 command reference documents cluster apply as omnigraph cluster apply --config <dir> --as <actor> (reference), so this job can fail during setup before the server ever starts; add an explicit actor such as --as ci here, or create the operator config first.

Useful? React with 👍 / 👎.


- name: Seed fixture data into each graph
run: |
set -euo pipefail
dir=/tmp/cluster
# `--mode` is required on load; local file:// writes need no `--yes`.
for g in alpha beta; do
omnigraph load \
--data packages/sdk/test/fixtures/data.jsonl \
--mode overwrite \
"$dir/graphs/${g}.omni"
done

- name: Start omnigraph-server (cluster) in background
run: |
set -euo pipefail
dir=/tmp/cluster
OMNIGRAPH_SERVER_BEARER_TOKEN=ci-token \
nohup omnigraph-server --config /tmp/ogm/omnigraph.yaml --bind 127.0.0.1:18081 \
> /tmp/ogm/server.log 2>&1 &
echo $! > /tmp/ogm/server.pid
nohup omnigraph-server --cluster "$dir" --bind 127.0.0.1:18080 \
> "$dir/server.log" 2>&1 &
echo $! > "$dir/server.pid"
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:18081/healthz > /dev/null; then
if curl -sf http://127.0.0.1:18080/healthz > /dev/null; then
echo "server up after ${i}s"; exit 0
fi
sleep 1
done
echo "server failed to start within 30s"; cat /tmp/ogm/server.log; exit 1
echo "server failed to start within 30s"; cat "$dir/server.log"; exit 1

- name: Run multi-graph e2e tests
- name: Run e2e tests (cluster, graphId=alpha)
env:
OMNIGRAPH_E2E: '1'
OMNIGRAPH_E2E_MULTIGRAPH: '1'
OMNIGRAPH_BASE_URL: http://127.0.0.1:18081
OMNIGRAPH_BASE_URL: http://127.0.0.1:18080
OMNIGRAPH_TOKEN: ci-token
OMNIGRAPH_GRAPH_ID: alpha
run: pnpm --filter @modernrelay/omnigraph run test

- name: Stop server
if: always()
run: |
if [ -f /tmp/ogm/server.pid ]; then
kill "$(cat /tmp/ogm/server.pid)" || true
if [ -f /tmp/cluster/server.pid ]; then
kill "$(cat /tmp/cluster/server.pid)" || true
fi

- name: Upload server log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: omnigraph-server-multigraph-log
path: /tmp/ogm/server.log
name: omnigraph-server-log
path: /tmp/cluster/server.log
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,11 @@ 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

# Live e2e against a real server (requires omnigraph-server v$(jq -r .omnigraph.serverVersion package.json) running):
OMNIGRAPH_E2E=1 OMNIGRAPH_BASE_URL=http://127.0.0.1:8080 OMNIGRAPH_TOKEN=$TOKEN pnpm --filter @modernrelay/omnigraph run test
# 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
# and .github/workflows/e2e.yml for the cluster.yaml + import/apply/load setup):
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.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"packageManager": "pnpm@9.15.0",
"omnigraph": {
"serverVersion": "0.6.1"
"serverVersion": "0.7.0"
},
"scripts": {
"sync-spec": "tsx scripts/sync-spec.ts",
Expand Down
19 changes: 9 additions & 10 deletions packages/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
const server = createOmnigraphMcpServer({
baseUrl: 'http://127.0.0.1:8080',
token: process.env.OMNIGRAPH_TOKEN,
graphId: 'alpha', // optionalset when talking to a multi-graph cluster
graphId: 'alpha', // requiredomnigraph-server 0.7.0 is cluster-only
});
await server.connect(new StdioServerTransport());
```
Expand All @@ -42,9 +42,9 @@ await server.connect(new StdioServerTransport());
| Variable | Purpose |
|---|---|
| `OMNIGRAPH_BASE_URL` | Required. `omnigraph-server` URL. |
| `OMNIGRAPH_GRAPH_ID` | Required (server 0.7.0+ is cluster-only). Graph this server operates on — routes every graph-scoped call under `/graphs/${id}/...`. The `bin` entrypoint refuses to start without it. |
| `OMNIGRAPH_TOKEN` | Optional bearer token. Required against a server with auth enabled. |
| `OMNIGRAPH_DEFAULT_BRANCH` | Branch used when a tool input omits one. Defaults to `main`. |
| `OMNIGRAPH_GRAPH_ID` | Optional. Target graph id in a multi-graph cluster — routes every graph-scoped call under `/graphs/${id}/...`. Leave unset for single-graph servers. |

## Surface

Expand All @@ -56,31 +56,30 @@ Read-only (`readOnlyHint: true`):
|---|---|
| `health` | Server liveness + version |
| `snapshot` | Snapshot of a branch (table list + row counts) |
| `query` | Run a `.gq` read query (canonical; successor to `read`) |
| `read` | Legacy alias for `query`. Field names are still `querySource` / `queryName`; prefer `query`. |
| `query` | Run a `.gq` read query |
| `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 |
| `graphs_list` | List registered graphs (multi-graph servers; requires `graph_list` policy) |
| `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 (canonical; successor to `change`) |
| `change` | Legacy alias for `mutate`. Accepts either legacy `querySource` / `queryName` or canonical `query` / `name`; mixed field families are rejected. Prefer `mutate`. |
| `ingest` | Bulk-ingest NDJSON (`mode: 'merge'` for idempotency) |
| `mutate` | Run a `.gq` mutation |
| `load` | Bulk-load NDJSON (`mode: 'merge'` for idempotency). 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` |
| `schema_apply` | Apply a schema migration. Optional `allowDataLoss: true` hard-drops column data for destructive steps; leave unset unless the plan was reviewed. |

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`.

### Resources

- `omnigraph://schema` — text/plain `.pg` source
- `omnigraph://branches` — application/json branch name list
- `omnigraph://graphs` — application/json `[{ graphId, uri }]` (multi-graph servers only; single-graph servers return 405)
- `omnigraph://graphs` — application/json `[{ graphId, uri }]` (requires a `graph_list` policy grant; the management surface is closed by default)

## License

Expand Down
13 changes: 8 additions & 5 deletions packages/mcp/cookbook-descriptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"exposed": {
"queries": {
"title": "GQ query best practices",
"description": "Read before authoring queries (via `read` or `change`). Covers .gq grammar, query name dispatch, parameter declaration, the PascalCase→lowerCamelCase edge casing trap, search functions requiring trailing `limit`, and the T12 non-nullable lint rule."
"description": "Read before authoring queries (via `query` or `mutate`). Covers .gq grammar, query name dispatch, parameter declaration, the PascalCase→lowerCamelCase edge casing trap, search functions requiring trailing `limit`, and the T12 non-nullable lint rule."
},
"data": {
"title": "Data ingest patterns",
"description": "Read before calling `ingest`. Covers mode selection (merge/append/overwrite), the branch→ingest→verify→merge loop for large/risky writes, embedding-staleness gotcha (mode:merge does not recompute embeddings), and change-vs-ingest decision matrix."
"title": "Data load patterns",
"description": "Read before calling `load`. Covers mode selection (merge/append/overwrite), the branch→load→verify→merge loop for large/risky writes, embedding-staleness gotcha (mode:merge does not recompute embeddings), and mutate-vs-load decision matrix."
},
"schema": {
"title": "Schema authoring and evolution",
Expand All @@ -22,8 +22,11 @@
}
},
"skipped": {
"aliases": "CLI-only — omnigraph.yaml + --alias mechanics, not actionable through the HTTP API the MCP wraps.",
"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."
"server-policy": "Server-deployment concerns (Cedar policy, auth tokens), out of scope for an MCP client.",
"cluster": "Operator/CLI concern — declaring and converging a deployment via cluster.yaml + `omnigraph cluster apply`. The MCP wraps the data-plane HTTP API, which cannot apply cluster config.",
"migrations": "Pre-0.7.0 → 0.7.0 migration reference for config files, CLI flags, and command spellings — a CLI/operator guide, not actionable through the HTTP API the MCP wraps.",
"stored-queries": "Stored-query declaration is a cluster/operator concern (cluster.yaml); the MCP wraps the data-plane HTTP API and does not currently expose a stored-query invoke tool."
}
}
2 changes: 1 addition & 1 deletion packages/mcp/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@modernrelay/omnigraph-mcp",
"version": "0.6.1",
"version": "0.7.0",
"description": "MCP server exposing an Omnigraph database to LLM clients (Tools + Resources, stdio transport).",
"license": "MIT",
"repository": {
Expand Down
Loading
Loading