GraphJin
OfficialAllows use of Auth0 as an identity provider for authentication to the GraphJin server.
Allows use of Google as an identity provider via OIDC for authentication.
Allows use of Keycloak as an identity provider for authentication.
Provides connectivity to MongoDB databases for AI-powered queries.
Provides connectivity to MySQL databases for AI-powered queries.
Allows use of Okta as an identity provider for authentication.
Enables AI assistants like GPT-4 to query databases through GraphJin.
Provides connectivity to PostgreSQL databases for AI-powered queries.
Provides connectivity to Snowflake databases for AI-powered queries.
Provides connectivity to SQLite databases for AI-powered queries.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@GraphJinlist the top 5 products by revenue"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
GraphJin - A Compiler to Connect AI to Your Databases
Point GraphJin at any database or source tree and AI assistants can query it instantly. Auto-discovers your schema, understands relationships, indexes code with tree-sitter, and compiles to optimized SQL. No configuration required.
Works with PostgreSQL, MySQL, MongoDB, SQLite, Oracle, MSSQL, Snowflake, BigQuery, S3/GCS/files, CodeSQL source indexes - and models from Claude/GPT-4 to local 7B models.
Installation
npm (all platforms)
npm install -g graphjinmacOS (Homebrew)
brew install dosco/graphjin/graphjinWindows (Scoop)
scoop bucket add graphjin https://github.com/dosco/graphjin-scoop
scoop install graphjinLinux
Download .deb/.rpm from releases
Docker
docker pull dosco/graphjinTry It Now
This is a quick way to try out GraphJin we'll use the --demo command which automatically
starts a database using docker and loads it with demo data.
Download the source which contains the webshop demo
git clone https://github.com/dosco/graphjin
cd graphjinNow launch the Graphjin service that you installed using the install options above
graphjin serve --demo --path examples/webshopYou'll see output like this:
GraphJin started
───────────────────────
Web UI: http://localhost:8080/
GraphQL: http://localhost:8080/api/v1/graphql
REST API: http://localhost:8080/api/v1/rest/
Workflows: http://localhost:8080/api/v1/workflows/<name>
MCP: http://localhost:8080/api/v1/mcp
Claude Desktop Configuration
────────────────────────────
Add to claude_desktop_config.json:
{
"mcpServers": {
"Webshop Development": {
"command": "/path/to/graphjin",
"args": ["mcp", "--server", "http://localhost:8080"]
}
}
}Copy the JSON config shown and add it to your Claude Desktop config file (see below for file location). You can also click File > Settings > Developer to get to it in Claude Desktop. You will also need to Restart Claude Desktop
OS | Possible config file locations |
macOS |
|
Windows |
|
Authenticate the CLI / MCP
Before graphjin cli or graphjin mcp can talk to a server, point them at one. There are no --server or --token flags — both come from a single saved config file (~/.config/graphjin/client.json, mode 0600):
graphjin cli setup http://localhost:8080 # local dev, no auth needed
graphjin cli setup https://graphjin.example.com # signs in via the server's OIDC IdPWhat setup does, depending on the server:
No built-in login (the server has
auth_login.enabled: false): saves only the URL. CLI calls send noAuthorizationheader.Built-in login enabled: kicks off an RFC 8628 device-code flow. The CLI prints a verification URL + short code, opens your browser, you sign in with the configured identity provider (Google, Okta, Keycloak, Auth0-as-IdP, Azure AD — anything OIDC), and the server mints a 30-day JWT. Both URL and JWT are saved to
client.json.
After setup every graphjin cli ... command just works:
graphjin cli health
graphjin cli query list
graphjin cli schema tables
graphjin cli setup show # print the saved config (token redacted)
graphjin cli setup logout # delete client.json
graphjin cli setup # re-run sign-in against the same server (refresh token)To enable built-in login, set this on the server:
auth:
jwt:
secret: "long-random-shared-secret" # used to sign and verify local JWTs
auth_login:
enabled: true
audience_graphjin: true # shorthand for audience: "graphjin-cli"
oidc:
issuer_url: "https://accounts.google.com"
client_id: "..."
client_secret: "..." # or $GJ_AUTH_LOGIN_OIDC_CLIENT_SECRET
allowed_domains: ["example.com"] # optional allow-listSuccessful authentication is recorded in structured logs with the verified email and name claims (when present), giving you a clean audit trail of who called every endpoint.
MCP install for OpenAI Codex + Claude Code
GraphJin includes a guided installer that configures MCP for OpenAI Codex, Claude Code, or both. Run graphjin mcp setup <server-url> first — mcp install reads the server URL from client.json, and the MCP-client config it writes is credential-free, so rotating tokens (re-running mcp setup) needs no edits to Claude / Codex.
graphjin mcp setup https://graphjin.example.com
graphjin mcp install # guided: target client + scopeOpenAI Codex
graphjin mcp install --client codex --scope global --yesClaude Code
graphjin mcp install --client claude --scope global --yesTroubleshooting
mcp installrequires a saved server URL — rungraphjin mcp setup <server-url>first if you see "no GraphJin server configured".The generated MCP-client config is
args: ["mcp"].graphjin mcpreads server + token fromclient.jsonon its own, in proxy mode when a server is saved or local mode when it's not.If Codex CLI does not support
codex mcp add --scope(older versions), GraphJin automatically falls back to updating:global scope:
~/.codex/config.tomllocal scope:
.codex/config.toml
Getting started
To use GraphJin with your own databases you have to first create a new GraphJin app, then configure it using its config files and then launch GraphJin.
Step 1: Create New GraphJin App
graphjin new my-appStep 2: Start the GraphJin Service
graphjin serve --path ./my-appStep 3: Add to Claude Desktop config file
Copy paste the Claude Desktop Config provided by graphjin serve into the Claude Desktop MCP config file. How to do this has been defined clearly above in the Try it Now section.
Step 4: Restart Claude Desktop
Step 5: Ask Claude questions like:
"What tables are in the database?"
"Show me all products under $50"
"List customers and their purchases"
"What's the total revenue by product?"
"Find products with 'wireless' in the name"
"Add a new product called 'USB-C Cable' for $19.99"
How It Works
Connects to database - Reads your schema automatically
Discovers relationships - Foreign keys become navigable joins
Exposes metadata -
gj_*tables make discovered databases, tables, columns, relationships, functions, and indexes queryable when the GraphJin source is enabledIndexes source code - CodeSQL turns tree-sitter syntax trees and database references into a managed SQLite database
Exposes MCP tools - Teach any LLM the query syntax
Runs JS workflows - Chain multiple GraphJin MCP tools in one reusable workflow
Compiles to SQL - Every request becomes a single optimized query
No resolvers. No ORM. No N+1 queries. Just point and query.
CodeSQL: Query Source Code Like a Database
CodeSQL is a managed source kind for source trees. Configure a source folder and GraphJin creates a SQLite cache under config/codesql/, indexes it with tree-sitter, and updates it on restart. In development it also watches for changes while the service runs; in production live watching is disabled.
sources:
- name: app
kind: sql
type: postgres
connection_string: postgres://app:secret@db/app
default: true
- name: code
kind: codesql
path: /srv/app
infer_db_refs: true
- name: graphjin
kind: graphjin
metadata: true
tables:
- name: users
source: app
- name: gj_code
source: code
read_only: trueGraphJin exposes CodeSQL through one ordinary GraphQL root, gj_code. Use kind to select files, symbols, references, imports, database references, docs, parse errors, change sets, and locks:
query {
gj_code(where: { kind: { eq: "symbol" }, name: { iregex: "handler|resolver" } }, limit: 20) {
name
symbol_kind
language
start_row
path
hash
}
}With a kind: graphjin source, GraphJin creates a read-only system graph named graphjin by default. Schema, catalog, entrypoint, capability, workflow, and system metadata are catalog items in gj_catalog; table and column metadata are selected by kind. When one CodeSQL source is active, GraphJin links catalog items to code references automatically:
query {
gj_catalog(where: { kind: { eq: "column" }, table_name: { eq: "users" }, column_name: { eq: "email" } }) {
database_name
table_name
column_name
gj_code {
kind
ref_kind
path
symbol_id
}
}
}This is where the model gets genuinely powerful: the same agent can inspect production data systems and the code that operates them. It can ask, "which handlers touch customer invoices?", "what tables do these workflows depend on?", or "show me the imports and call sites near this data path" without switching tools or inventing a new backend.
What AI Can Do
Simple queries with filters:
{ products(where: { price: { gt: 50 } }, limit: 10) { id name price } }Nested relationships:
{
orders(limit: 5) {
id total
customer { name email }
items { quantity product { name category { name } } }
}
}Aggregations:
{ products { count_id sum_price avg_price } }Analytics directives:
{
orders {
account_id
month
total
running_total: total @running(aggregate: sum, by: "account_id", orderBy: { month: asc })
moving_avg_total: total @moving(aggregate: avg, rows: 6, by: "account_id", orderBy: { month: asc })
previous_total: total @previous(by: "account_id", orderBy: { month: asc })
rank_by_total: total @rank(by: "account_id", order: desc)
}
}Use analytics directives when each original row should remain visible while adding report metrics such as running totals, moving averages, previous/next values, first/last values, and rank within a group. Ordinary one-row-per-group summaries still use distinct plus aggregate fields. Supported SQL databases validate analytics support at compile time; MongoDB and known-old database versions return clear errors.
Mutations:
mutation {
products(insert: { name: "New Product", price: 29.99 }) { id }
}Spatial queries:
{
stores(where: { location: { st_dwithin: { point: [-122.4, 37.7], distance: 1000 } } }) {
name address
}
}Real-time Subscriptions
Get live updates when your data changes. GraphJin handles thousands of concurrent subscribers with a single database query - not one per subscriber.
subscription {
orders(where: { user_id: { eq: $user_id } }) {
id total status
items { product { name } }
}
}Why it's efficient:
Traditional approach: 1,000 subscribers = 1,000 database queries
GraphJin: 1,000 subscribers = 1 optimized batch query
Automatic change detection - updates only sent when data actually changes
Built-in cursor pagination for feeds and infinite scroll
Subscribe over WebSockets (graphql-ws / graphql-transport-ws subprotocols) or Server-Sent Events — set Accept: text/event-stream on a POST /api/v1/graphql request and GraphJin streams event: next frames for each result, terminated by event: complete. Works from Node.js, Go, or any browser EventSource / WebSocket client.
Filesystem Tables (Local, S3, GCS)
Object stores show up as ordinary tables in your GraphQL schema. Declare them in config and they get the same query surface as a database table — no per-storage GraphQL plumbing on your side.
sources:
- name: avatars
kind: filesystem
backend: s3
bucket: my-bucket
prefix: avatars/
region: us-east-1
presign_ttl: 15m
- name: invoices
kind: filesystem
backend: gcs
bucket: invoices
prefix: 2026/
- name: uploads_local
kind: filesystem
backend: local
root: /var/lib/graphjin/uploads
tables:
- name: avatars
source: avatars
read_only: true
- name: invoices
source: invoices
read_only: true
- name: uploads_local
source: uploads_localEvery filesystem table exposes the same columns regardless of backend:
{ avatars(
where: { key: { like: "users/%" } }
order_by: { key: asc }
limit: 50
) {
key size content_type modified_at url
}
}
{ avatars(id: "users/42.png") {
key size url data # data is base64 because the field was selected
}
}The legacy prefix, key, and inline_data arguments remain accepted, but new callers should use the normal GraphJin read surface: id, where, order_by, limit, offset, first, last, after, and before.
For cursor pagination, request the standard root cursor field, e.g. avatars_cursor, and pass it back through after: $cursor.
url is a presigned GET URL by default (15 min, configurable per table). Auth follows the standard credential chain: AWS env / ~/.aws / IRSA / EC2 IMDS for S3, Application Default Credentials for GCS — never embedded in GraphJin config.
Slim builds drop SDK weight: -tags no_s3 or -tags no_gcs excludes either backend. Custom backends register through core.OptionSetFilesystemBackend(name, factory) — same SDK GraphJin uses for the built-ins.
File Uploads
The GraphQL endpoint accepts multipart bodies per the graphql-multipart-request-spec. Files can be inlined as base64 (default) or streamed straight to a filesystem table:
uploads:
enabled: true
storage: avatars # name of a filesystems[] entry; omit to inline as base64
storage_key_prefix: "{date}/" # {date} → YYYY/MM/DD
max_size: 25_000_000
allowed_mime: ["image/*", "application/pdf"]When storage is set, the file body is written to the backend and the GraphQL variable becomes a stable reference — mutations persist this directly into a JSONB column:
{ "key": "2026/05/08/abc123.png",
"url": "https://s3.../...?presigned",
"size": 12345,
"content_type": "image/png" }When storage is empty the variable carries the bytes inline as {filename, content_type, size, data} (base64) — useful for small uploads going straight into bytea.
Apollo Federation v2
GraphJin can register as a federation subgraph so it composes with other services behind Apollo Router / Cosmo / Hive Gateway:
federation:
enabled: true
version: "v2.5"
keys:
users: ["id"] # auto-derived from PKs by default
orders: ["id", "tenant_id"] # composite keys via override
shareable: ["Tag.name"] # field-level @shareable
inaccessible: ["Users.encrypted_password"]_service { sdl } returns a federation-flavoured SDL with @link, @key, @shareable, @inaccessible, @tag, _Service, and _Entity. Composition succeeds out of the box; _entities resolution is on the roadmap (the engine returns a clear error today, so gateways see the gap rather than silent failures).
HTTP API Routes
graphjin serve exposes everything under a single host/port. All routes go through the configured auth handler unless noted.
Route | Methods | Purpose |
|
| GraphQL queries and mutations. Subscriptions if the request is a WebSocket upgrade or carries |
|
| Run a saved/persisted query by name. Variables go in |
|
| Legacy workflow execution endpoint. In source mode it is registered only when |
|
| OpenAPI 3 spec generated from your saved REST queries. |
|
| MCP (Model Context Protocol) HTTP transport — Streamable HTTP, stateless. |
|
| MCP HTTP transport for stateless message integrations. |
|
| Legacy discovery document. In source mode it is registered only when |
|
| Legacy discovery drill-down (e.g. |
|
| Admin: list known tables (Web UI). |
|
| Admin: schema for a single table. |
|
| Admin: list saved queries. |
|
| Admin: details for a saved query. |
|
| Admin: list GraphQL fragments. |
|
| Admin: effective runtime config. |
|
| Admin: connected database info. |
|
| OIDC device-flow start (only if |
|
| OIDC device-flow poll. |
|
| OIDC login redirect. |
|
| OIDC callback. |
|
| Liveness probe. No auth. |
|
| Built-in Web UI (only when |
Mode flags that change which routes are live:
mcp.disable: true— removes/api/v1/mcpand/api/v1/mcp/message.mcp.only: true— keeps only/healthand/api/v1/mcp*. Legacy/api/v1/workflows/*and/api/v1/discovery*remain only whenmcp.legacy_discovery: true.Source mode (
sources:present) disables legacy/api/v1/workflows/*and/api/v1/discovery*unlessmcp.legacy_discovery: true.webui: false— drops/and the/api/v1/admin/*routes.
MCP Tools
GraphJin exposes a catalog-first agent surface that guides AI models to discover before acting. Start with query_catalog, then inspect evidence with get_catalog_card before writing queries, choosing relationships, or using GraphJin-specific syntax. For actions, agents can use GraphJin control-plane GraphQL roots such as gj_workflow_execution(insert), gj_workflow(insert/update/delete), and gj_config(id: "current", update: ...). Schema reloads, schema changes, where-clause validation, and query repair remain MCP action tools. The legacy discovery tools are migration shims and are disabled unless mcp.legacy_discovery: true.
For teams building MCP agents, internal copilots, workflow agents, or enterprise automation, see AGENTIC.md. It explains the catalog-first agent loop in detail: discover, inspect, validate, act, observe, and refine.
Key discovery tools:
get_catalog_entrypointsto choose a discovery path when the task is broadquery_catalogto search schema, relationship, workflow, language, config, policy, capability, and query-pattern items. Usesearchfor ranked text discovery andwherefor exact filters.get_catalog_cardto inspect evidence, examples, details, safety notes, and graph edgesvalidate_where_clauseto validate filters before execution
For JS orchestration, use:
query_catalogwithwhere: { kind: { eq: "workflow" } }to discover reusable workflowsget_js_runtime_apito discover exactly which globals/functions are available inside workflow scriptsgj_workflow_execution(insert: { workflow_name: "...", variables: {...} })to run./workflows/<name>.jsthrough GraphQL. This is mutation-only and returns an ephemeral result row; it does not store run history. Mark the workflows source orgj_workflow_executiontableread_onlyto block it. Theexecute_workflowMCP compatibility tool is available only whenmcp.legacy_discovery: trueandmcp.allow_workflow_execution: true.
Prompts like write_query and fix_query_error help models construct and debug queries.
JS Workflows (GraphQL + REST)
Workflows let an LLM run multi-step logic in JavaScript while still using GraphJin MCP tools for DB-aware operations.
Create a file in ./workflows, for example ./workflows/customer_insights.js:
function main(input) {
const tables = gj.tools.queryCatalog({
where: { kind: { eq: "table" } }
}).cards;
const top = gj.tools.executeSavedQuery({
name: "top_customers",
variables: { limit: input.limit || 5 }
});
return { tables, top };
}Run via GraphQL
mutation {
gj_workflow_execution(insert: {
workflow_name: "customer_insights"
variables: { limit: 5 }
}) {
status
result_json
error
}
}Legacy MCP clients can call execute_workflow only when mcp.legacy_discovery: true.
Run via REST
This legacy HTTP endpoint is available in legacy database mode, or in source mode only when mcp.legacy_discovery: true.
POST /api/v1/workflows/<name>with JSON bodyGET /api/v1/workflows/<name>?variables={...json...}
Both map variables to the same workflow input object.
Chat Walkthroughs
Real conversations showing how an AI assistant uses GraphJin MCP tools to help you go from zero to running queries.
"I have databases running but don't know the details"
You: I have some databases running locally but I'm not sure about the ports or connection details. Can you help me set up GraphJin?
Claude: Let me scan your system to find running databases.
calls
discover_databasesI found 2 databases on your system:
PostgreSQL on localhost:5432 (TCP) — listening, auth: ok (user: postgres), databases:
myapp,myapp_testMySQL on localhost:3306 (TCP) — listening, auth: auth_failed — default credentials failed, provide username and password
The PostgreSQL instance has databases
myappandmyapp_test. Which one would you like to configure GraphJin with?
You: Let's go with the "myapp" database on Postgres.
Claude: I'll configure GraphJin to connect to your PostgreSQL database.
calls
update_current_configwith databases: {"main": {type: "postgres", host: "localhost", port: 5432, user: "postgres", dbname: "myapp"}}Connected successfully! Let me discover the catalog surface.
calls
query_catalogwithwhere: {kind: {eq: "table"}}Your database has these tables:
users,orders,products,reviews. Want me to write a query?
You: Show me users with their orders.
Claude: calls
query_catalogwithsearch: "join users orders", where: {kind: {eq: "relationship"}}, thenget_catalog_card, thenexecute_graphql{ users { id name email orders { id total status } } }Here are the results — 24 users returned, each with their associated orders.
"I have a Docker Compose project with MySQL"
You: I'm running a Docker Compose stack with MySQL. Help me connect GraphJin to it.
Claude: Let me check what database containers you have running.
calls
discover_databasesFound a MySQL Docker container:
MySQL via Docker — container
myapp-db-1, imagemysql:8.0, port mapped to localhost:13306I'll configure GraphJin with the discovered connection details.
calls
update_current_configwith databases: {"main": {type: "mysql", host: "localhost", port: 13306, user: "root", dbname: ""}}What's the database name and password for your MySQL instance?
You: Database is "shop_db", password is "secret123".
Claude: calls
update_current_configwith the full credentialsConnected! Let me explore the catalog.
calls
query_catalogwithwhere: {kind: {eq: "table"}}Tables found:
customers,products,orders,order_items,categories. Let me check how they relate.calls
query_catalogwithsearch: "join orders customers order_items products", where: {kind: {eq: "relationship"}}, thenget_catalog_cardfor the relevant relationship items
ordersconnects tocustomers(via customer_id) andorder_items(via order_id), andorder_itemslinks toproducts. Here's a nested query:{ orders { id total customer { name email } order_items { quantity product { name price } } } }
"I want to query my SQLite app database"
You: I have a SQLite database file in my project. Can GraphJin work with it?
Claude: Absolutely! Let me scan your project directory for SQLite files.
calls
discover_databaseswith scan_dir: "."Found 1 SQLite database:
data/app.sqlite3 — file found
I'll configure GraphJin to use it.
calls
update_current_configwith databases: {"main": {type: "sqlite", path: "data/app.sqlite3"}}Connected! Let me see your tables.
calls
query_catalogwithwhere: {kind: {eq: "table"}}Tables:
tasks,projects,tags,task_tags. Want to query something?
You: Show me all tasks with their project name, sorted by due date.
Claude: calls
query_catalogwithsearch: "join tasks projects", where: {kind: {eq: "relationship"}}, thenget_catalog_card, thenexecute_graphql{ tasks(order_by: {due_date: asc}) { id title due_date completed project { name } } }Here are your 12 tasks sorted by due date, each showing the parent project name.
Database Support
Database | Queries | Mutations | Subscriptions | Full-Text | GIS |
PostgreSQL | Yes | Yes | Yes | Yes | PostGIS |
MySQL | Yes | Yes | Yes | Yes | 8.0+ |
MariaDB | Yes | Yes | Yes | Yes | Yes |
MSSQL | Yes | Yes | Yes | No | Yes |
Oracle | Yes | Yes | Yes | No | Yes |
SQLite | Yes | Yes | Yes | FTS5 | SpatiaLite |
MongoDB | Yes | Yes | Yes | Yes | Yes |
Snowflake | Yes | Yes | No | No | No |
BigQuery | Yes (experimental) | No | No | No | No |
CockroachDB | Yes | Yes | Yes | Yes | No |
Also works with AWS Aurora/RDS, Google Cloud SQL, and YugabyteDB. Snowflake supports key pair (JWT) authentication. BigQuery support is experimental and query-focused.
Production Security
Query allow-lists - In production, only saved queries can run. AI models call execute_saved_query with pre-approved queries. No arbitrary SQL injection possible.
Source-mode access - New multi-user deployments should use sources: with request-wide identity and source-level access defaults. GraphJin compiles those defaults into the existing qcode/SQL enforcement path, so account filters and trusted mutation presets are enforced by the generated database query.
identity:
user_id_claim: sub
role_claims: [role, roles]
namespace_claim: account_id
sources:
- name: app
kind: database
access:
read: account
write: blocked
delete: blocked
namespace_column: account_idSee SECURITY.md for the security model and Source Mode Migration for legacy roles[].tables migration steps. In source mode, user-written roles[].tables rules are rejected intentionally.
JWT authentication - Supports Auth0, Firebase, JWKS endpoints.
Response caching - Redis with in-memory fallback. Automatic cache invalidation on mutations. Stale-while-revalidate support: serve cached responses immediately while a background worker refreshes the entry — concurrent refreshes for the same key are deduplicated via singleflight, and the worker pool is bounded so a thundering herd can't spawn unbounded goroutines.
caching:
ttl: 3600 # hard expiry in seconds
fresh_ttl: 300 # soft expiry — entries past this trigger SWR refreshAlso a GraphQL API
GraphJin works as a traditional API too - use it from Go or as a standalone service.
Go
go get github.com/dosco/graphjin/core/v3db, _ := sql.Open("pgx", "postgres://localhost/myapp")
gj, _ := core.NewGraphJin(nil, db)
res, _ := gj.GraphQL(ctx, `{ users { id email } }`, nil, nil)Standalone Service
brew install dosco/graphjin/graphjin # Mac
graphjin new myapp && cd myapp
graphjin serveBuilt-in web UI at http://localhost:8080 for query development.
Documentation
Get in Touch
License
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/dosco/graphjin'
If you have feedback or need assistance with the MCP directory API, please join our Discord server