governance-query-mcp
Provides read-only query tools over two Notion databases — an audit trail of dated, reasoned changes (CB-DB) and an action queue of open items (AQ). Enables filtered, server-side Notion database queries with live-validated select and status options, date-range and in-force filtering, sorted results with a configurable limit, and single-row lookup by id, returning flattened JSON rows with fields such as description, effective date, subsystem, status, owner, priority and Notion URL. Never writes to Notion.
Click on "Deploy 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., "@governance-query-mcpshow me audit trail rows for subsystem Payments"
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.
governance-query-mcp
A read-only Model Context Protocol server that exposes two Notion databases, an audit trail and an action queue, as four query tools. It runs locally over stdio and talks to the Notion REST API directly.
What it is
I keep the governance record for BREALLE, the automation practice I run, in two Notion databases: an audit trail of dated, reasoned changes (CB-DB) and an action queue of open items (AQ). The standard Notion connectors for Claude have no row-query primitive. A question like "which audit rows for this subsystem are still in force" degrades to semantic search or a fetch of the whole database page, and both are slow, lossy and metered.
This server closes that gap. Each tool builds a server-side Notion filter from its parameters, sends it to POST /v1/databases/{id}/query, and maps each page object to a flat JSON row. Every enum parameter is checked against the live database schema before a query runs, so a typo or a renamed option comes back as an error with the allowed values instead of an empty result.
The server never writes to Notion. The integration token stays on the server side; it is never returned in a response and never written to a log.
Related MCP server: Notion Remote MCP Server
The four tools
cb_db_query
Filtered read of the audit trail, sorted by Effective Date descending. All parameters are optional.
Parameter | Type | Meaning |
| string | Subsystem option, validated live |
| string | Status option, validated live |
| boolean | Keep only rows whose In Force formula matches; default |
| string | ISO 8601 date; Effective Date on or after this day |
| string | Change Type option, validated live |
| string | Artifact Type option, validated live |
| string | Authorized By option, validated live |
| integer | Rows to return; default 20, maximum 100 |
Returns { results, count, truncated, query_echo }. Each result carries the row's id, description, effective date, subsystem, change type, artifact URL and type, sections affected, summary, reasoning, authorizer, source session, supersedes, status, in-force flag and Notion URL.
cb_db_get
One audit-trail row by id. The single parameter cb_id accepts the prefixed form or the bare number. Returns { result } or a not_found error.
aq_query
Filtered read of the action queue, sorted by Created descending. All parameters are optional.
Parameter | Type | Meaning |
| string | Owner option, validated live |
| string | Status option, validated live |
| string | Priority option, validated live |
| string | Category option, validated live |
| string | Type option, validated live |
| string | ISO 8601 date; Created on or after this day |
| integer | Rows to return; default 20, maximum 100 |
Returns { results, count, truncated, query_echo }. Each result carries the item's id, action title, status, owner, priority, category, type, created and resolved dates, context link, blocked-by text, decision notes and Notion URL.
aq_get
One action-queue row by id. The single parameter aq_id accepts the prefixed form or the bare number. Returns { result } or a not_found error.
Errors
Every error has the same shape: { error: { code, message, details } }. The codes are invalid_parameter, not_found, unauthorized, rate_limited, upstream_notion_error and internal. An internal error carries a correlation_id that matches one server-side log line; Notion's own error text is logged there and never passed through to the caller.
Enum validation against the live schema
No option value is hardcoded anywhere in the server. On first use it calls GET /v1/databases/{id}, reads the option names of every select and status property, and caches them for SCHEMA_CACHE_TTL_SECONDS (default 300). A parameter such as change_type is checked against that set before the filter is built.
The reason is how Notion answers a filter on an option that does not exist: it does not reject it, it returns an empty result set, which is indistinguishable from "no rows matched". An agent that reads that answer will conclude a change was never filed. With validation, an unknown value returns invalid_parameter with the current allowed set in details.allowed, so the caller can correct itself in one step. It also means that when I rename or add an option in Notion the server follows it on the next cache refresh with no code change.
The one filter that cannot be pushed to Notion is in_force. Notion refuses to filter on a formula property of unknown type, so cb_db_query streams the server-filtered, date-sorted pages and applies the in-force predicate to each row's formula output until it has limit matches. See Limits below.
Running it locally over stdio
You need Node.js 20 or later and a Notion internal integration that has been shared with the two databases. Read access is enough.
Install and build.
npm install npm run buildCopy
.env.exampleto.envand fill it in: the integration token, and the two database ids. The ids are the 32-character strings in each database's URL; the file ships with placeholders.Register the server with a client. For Claude Code:
claude mcp add -s user governance-query -- node --env-file=/path/to/governance-query-mcp/.env /path/to/governance-query-mcp/dist/stdio.jsFor Claude Desktop, add an entry to
mcpServersin its config file:{ "mcpServers": { "governance-query": { "command": "node", "args": [ "--env-file=/path/to/governance-query-mcp/.env", "/path/to/governance-query-mcp/dist/stdio.js" ] } } }
The client spawns the process on demand and it exits with the session; nothing listens on a port. Under stdio, stdout is the JSON-RPC channel, so every log line goes to stderr.
npm test runs the unit tests for the row mappers with no network. npm run typecheck and npm run lint cover the rest. docs/clients.md has the client configuration in more detail and docs/smoke-tests.md has the acceptance fixtures.
One quirk: the config loader requires MCP_BEARER_TOKEN to be set even under stdio, where it is not used. Any non-empty value satisfies it.
The HTTP entrypoint
dist/index.js, the Dockerfile and docker-compose.yml are the hosted mode: Streamable HTTP at POST /mcp, legacy SSE at GET /sse plus POST /messages, an unauthenticated /health, and a bearer token compared in constant time on every other request. It is kept in the tree and still builds, but it is not how I run the server now. docs/auth.md has the history.
Notion API version
The server uses the classic single-data-source endpoints, GET /v1/databases/{id} and POST /v1/databases/{id}/query, with Notion-Version pinned to 2022-06-28. The CB_DB_ID and AQ_DB_ID variables therefore carry database ids, not data-source ids. If Notion retires those semantics, the change is to switch NOTION_VERSION and move the query to POST /v1/data_sources/{id}/query.
Deployment history
Ran May to September 2026 as a hosted service behind TLS and a bearer token, on a server that was retired when the client work ended; local over stdio since.
Limits
No pagination. There is no cursor parameter. A query returns at most 100 rows (
limitis clamped to 100) and reportstruncated: truewhen more rows matched, but there is no way to ask for the next page.Enumeration ceiling. Because of the point above, the newest 100 rows that match a filter are all that can be listed. For a full enumeration I use a Notion view instead.
In-force filtering is client-side.
cb_db_queryfetches pages of 100 and drops rows whose formula does not match, up to 20 pages. On a database of several hundred rows this is usually one request, because the scan stops as soon as it haslimitmatches; a filter that most rows fail would take more pages, and on a much larger database the cap could cut the scan short.One request at a time. Calls to Notion are serialized with a 350 ms minimum spacing to stay under the integration rate limit, and a 429 is retried up to three times honoring
Retry-After. Throughput is low by design.Schema cache lag. An option added in Notion is not accepted until the cache expires, at most
SCHEMA_CACHE_TTL_SECONDSlater.Local clients only. A stdio server is reachable from the machine it runs on. The hosted HTTP mode could not be used from the claude.ai web connector either, because custom connectors there require OAuth 2.1 with PKCE and this server only ever implemented a static bearer token.
Authorship
I specified this server and directed the build; the code was written by AI coding agents under my direction, and I own and run it daily.
License
MIT. See LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Query and audit AppSheet apps in natural language via Knotrik's pre-scanned definitions.
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
A read-only verified record of agent-operable GTM tools: search, fetch, compare, track changes.
Query 40 databases from Claude, ChatGPT, or Cursor — on any device. Read-only, encrypted, audited.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables interaction with Notion workspaces through the Notion API. Provides tools to search, read, create, and update Notion pages and databases with real-time synchronization.2,433 npmMIT
- FlicenseNot gradedqualityNot gradedmaintenanceEnables secure interaction with Notion through a suite of tools for searching, reading, and modifying pages and databases. It features OAuth 2.1 authentication with PKCE and encrypted token storage to provide an enterprise-ready interface for MCP clients.-
- FlicenseNot gradedqualityDmaintenanceProvides structured, SQL-style query access to Notion databases, allowing Claude to filter, sort, and update pages through the Notion REST API. It enables advanced property-based filtering and database schema discovery beyond the capabilities of standard connectors.-
- FlicenseNot gradedqualityDmaintenanceProvides tools for querying and updating Notion database rows by exact property filters, avoiding semantic search for reliable row selection.-