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
10 changes: 8 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ jobs:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Verify package versions match pinned server
run: pnpm run check-versions
- name: Verify spec matches upstream pinned tag
run: pnpm run check-drift
- name: Verify generated SDK is up to date
Expand All @@ -27,11 +29,15 @@ jobs:
stale=1
fi
if ! git diff --quiet packages/sdk/src/version.gen.ts; then
echo "version.gen.ts is stale (run 'pnpm run gen-version')"
echo "packages/sdk/src/version.gen.ts is stale (run 'pnpm run gen-version')"
stale=1
fi
if ! git diff --quiet packages/mcp/src/version.gen.ts; then
echo "packages/mcp/src/version.gen.ts is stale (run 'pnpm run gen-version')"
stale=1
fi
if [ "$stale" -ne 0 ]; then
git diff packages/sdk/src/generated packages/sdk/src/version.gen.ts
git diff packages/sdk/src/generated packages/sdk/src/version.gen.ts packages/mcp/src/version.gen.ts
exit 1
fi
- name: Verify every operation has a facade binding
Expand Down
168 changes: 165 additions & 3 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,44 @@ jobs:
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

- name: Start omnigraph-server in background
- 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'
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/og/omnigraph.yaml <<'YAML'
policy:
file: /tmp/og/policy.yaml
graphs:
e2e:
uri: /tmp/og/repo.omni
cli:
graph: e2e
branch: main
YAML

- name: Start omnigraph-server in background (single-graph)
run: |
set -euo pipefail
OMNIGRAPH_SERVER_BEARER_TOKEN=ci-token \
nohup omnigraph-server /tmp/og/repo.omni --bind 127.0.0.1:18080 \
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
Expand All @@ -73,7 +106,7 @@ jobs:
done
echo "server failed to start within 30s"; cat /tmp/og/server.log; exit 1

- name: Run e2e tests
- name: Run single-graph e2e tests
env:
OMNIGRAPH_E2E: '1'
OMNIGRAPH_BASE_URL: http://127.0.0.1:18080
Expand All @@ -93,3 +126,132 @@ jobs:
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'
version: 1
groups:
ci: [default]
rules:
- id: ci-can-list-graphs
allow:
actors: { group: ci }
actions: [graph_list]
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'
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
graphs:
alpha:
uri: /tmp/ogm/alpha.omni
policy:
file: /tmp/ogm/per-graph-policy.yaml
beta:
uri: /tmp/ogm/beta.omni
policy:
file: /tmp/ogm/per-graph-policy.yaml
YAML

- name: Start omnigraph-server in multi-graph mode
run: |
set -euo pipefail
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
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:18081/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

- name: Run multi-graph e2e tests
env:
OMNIGRAPH_E2E: '1'
OMNIGRAPH_E2E_MULTIGRAPH: '1'
OMNIGRAPH_BASE_URL: http://127.0.0.1:18081
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
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
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ 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
- run: pnpm run check-coverage
- run: pnpm run build
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
},
"packageManager": "pnpm@9.15.0",
"omnigraph": {
"serverVersion": "0.4.2"
"serverVersion": "0.6.0"
},
"scripts": {
"sync-spec": "tsx scripts/sync-spec.ts",
"check-drift": "tsx scripts/check-drift.ts",
"check-versions": "tsx scripts/check-versions.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",
Expand Down
23 changes: 19 additions & 4 deletions packages/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ MCP server exposing an [Omnigraph](https://github.com/ModernRelay/omnigraph) dat
"env": {
"OMNIGRAPH_BASE_URL": "http://127.0.0.1:8080",
"OMNIGRAPH_TOKEN": "your-bearer-token",
"OMNIGRAPH_DEFAULT_BRANCH": "main"
"OMNIGRAPH_DEFAULT_BRANCH": "main",
"OMNIGRAPH_GRAPH_ID": "alpha"
}
}
}
Expand All @@ -31,10 +32,20 @@ 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', // optional — set when talking to a multi-graph cluster
});
await server.connect(new StdioServerTransport());
```

### Env vars

| Variable | Purpose |
|---|---|
| `OMNIGRAPH_BASE_URL` | Required. `omnigraph-server` URL. |
| `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

### Tools
Expand All @@ -45,27 +56,31 @@ Read-only (`readOnlyHint: true`):
|---|---|
| `health` | Server liveness + version |
| `snapshot` | Snapshot of a branch (table list + row counts) |
| `read` | Run a `.gq` read query |
| `query` | Run a `.gq` read query (canonical; successor to `read`) |
| `read` | Legacy alias for `query`. Field names are still `querySource` / `queryName`; prefer `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) |

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

| Tool | Purpose |
|---|---|
| `change` | Run a `.gq` mutation |
| `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) |
| `branches_create` | Create a new branch |
| `branches_delete` | Delete a branch |
| `branches_merge` | Merge `source` into `target` |
| `schema_apply` | Apply a schema migration |
| `schema_apply` | Apply a schema migration. Optional `allowDataLoss: true` hard-drops column data for destructive steps; leave unset unless the plan was reviewed. |

### 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)

## License

Expand Down
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.4.1",
"version": "0.6.0",
"description": "MCP server exposing an Omnigraph database to LLM clients (Tools + Resources, stdio transport).",
"license": "MIT",
"repository": {
Expand Down
6 changes: 4 additions & 2 deletions packages/mcp/src/bin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env node
// Stdio MCP server entrypoint. Reads OMNIGRAPH_BASE_URL / OMNIGRAPH_TOKEN /
// OMNIGRAPH_DEFAULT_BRANCH from the environment; clients invoke this binary
// as a subprocess and speak JSON-RPC over stdin/stdout.
// OMNIGRAPH_DEFAULT_BRANCH / OMNIGRAPH_GRAPH_ID from the environment;
// clients invoke this binary as a subprocess and speak JSON-RPC over
// stdin/stdout.

import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { createOmnigraphMcpServer } from './server';
Expand All @@ -16,6 +17,7 @@ const server = createOmnigraphMcpServer({
baseUrl,
token: process.env.OMNIGRAPH_TOKEN,
defaultBranch: process.env.OMNIGRAPH_DEFAULT_BRANCH,
graphId: process.env.OMNIGRAPH_GRAPH_ID,
});

await server.connect(new StdioServerTransport());
Loading
Loading