Query & MCP surface
GraphQL over MCP, compiled to SQL/PGQ by gopgql. CodiQ builds no MCP layer of its own, and the surface never writes.
The read side is owned entirely bygopgql. It reads the same SDL that generated the tables, compiles GraphQL selections to PostgreSQL 19GRAPH_TABLE(… MATCH …) queries, and serves them over MCP. CodiQ contributes the model and the rows; it writes no query layer, no resolver and no MCP server.
Three layers, one SDL
gopgql generate → goose migrations, applied bygopgql migrate.CREATE PROPERTY GRAPH app_graph over those base tables, mapping each edge to occurrence/file identity with SOURCE KEY … REFERENCES … andDESTINATION KEY … REFERENCES …. Its own migration, numbered after the tables.gopgql-mcp --sdl schema/codiq.graphql, over the streamable HTTP transport.CREATE PROPERTY GRAPH defines a view over plain vertex and edge tables; all mutation is ordinary DML on those base tables. CodiQ's writes never touch the view, and the MCP server's pool opens withdefault_transaction_read_only=on — so ingestion stays the only writer even when both share credentials. Writes are never agent-triggered.Query shape
gopgql derives each root field from the table name rather than pluralising the label, and the SDL pins the table names to the singular ones §4.4 uses. So the roots read file, occurrence andscope, and every field is the camelCase name from the SDL —symbolKind, pkgName, resolvesTo,calledBy.
Nesting extends one MATCH chain rather than spawning a second query, so a multi-hop traversal is still a singleGRAPH_TABLE. Argument values travel as bind parameters and are never interpolated into SQL. The graph is named app_graph — gopgql's default, and not configurable: there is no --graph flag on gopgql-mcp. You only need the name to write SQL/PGQ by hand against the database; the GraphQL surface never mentions it. A row with no edge of the selected kind comes back with an empty list rather than dropping out, so an emptycalledBy reads as "nothing calls this" and not as "no such symbol".
Two tools
introspectWhat the schema exposes, answered from the SDL without touching the database. No arguments gives the overview — every root field with its arguments; type: "Occurrence" drills into one type;format: "sdl" returns the schema document.queryRuns a GraphQL query and returns the data. Query operations only — the mapped graph is read-only. Values belong in variables, which are bound as SQL parameters. The standard introspection meta-fields (__schema, __type, __typename) work here too.Pointing an agent at it
The server is long-running and speaks streamable HTTP, so several agents can share one process. With the stack fromrun it locally up, the endpoint is:
There is a /healthz endpoint on the same port, which the compose healthcheck uses.
The demo walks four exchanges against the seeded corpus, with the responses recorded from a running server.