Skip to content

feat: remote MCP server for governed Postgres access#18

Merged
tianzhou merged 9 commits into
mainfrom
design/mcp-server
Jun 24, 2026
Merged

feat: remote MCP server for governed Postgres access#18
tianzhou merged 9 commits into
mainfrom
design/mcp-server

Conversation

@tianzhou

@tianzhou tianzhou commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Exposes pgconsole as a remote MCP server (Streamable HTTP, mounted on the existing Express app) so external AI agents (Claude Code, Cursor, VS Code Copilot, etc.) can operate on the Postgres connections pgconsole manages — with the same governance the UI enforces: per-connection IAM, per-statement SQL permission detection, and audit logging.

Identity: first-class agents

MCP callers authenticate with a bearer token belonging to an [[agents]] principal — a non-human identity that is not a user (no UI login, no license seat). Two modes, matching the standard AI-agent delegation model:

  • Pure agent — a standalone service account (e.g. CI bot). Authorized only by IAM rules whose members include agent:<id> (*/group:/user: rules never apply to agents). Audited as agent:<id>.
  • Delegated agent (on_behalf_of) — acts for a user, inheriting that user's permissions narrowed by optional permissions/connections caps. Can never exceed the user and deprovisions automatically with them ("persona shadowing"). Audited as the user, tagged with the agent.
[[agents]]                         # pure — granted by an agent: IAM rule
id = "migration-bot"
token = "pgc_mcp_…"

[[agents]]                         # delegated — bounded by alice, read-only on prod
id = "alice-claude"
token = "pgc_mcp_…"
on_behalf_of = "alice@example.com"
permissions = ["read"]
connections = ["prod"]

Tools (filtered per agent)

Discovery: list_connections, list_objects (paginated/filterable), describe_table.
Execution, one per IAM permission: explain_query (explain), query (read), write_data (write), run_ddl (ddl).

Enforcement

Every execution tool runs the SQL through pgconsole's parser-based permission detection first:

  1. Each statement's primary permission must equal the tool's permission — blocks smuggling a DROP through query and rejects mixed-class batches.
  2. The full required set (incl. function-derived, e.g. pg_terminate_backendadmin) must be a subset of the agent's effective grant.
  3. Audited, tagged source=mcp + tool + agent.

explain_query is restricted to a single read statement; analyze additionally requires everything running it would.

Testing

  • 466 unit tests pass — covers agent parsing/validation, the agent: IAM member type, pure-agent permission resolution, delegated caps, per-token tool selection, and primaryPermissions.
  • Verified live against local Postgres: pure + delegated agents; delegated permissions/connections caps enforced (tool list filtered, write_data denied, off-cap connection denied); all enforcement rejections.

🤖 Generated with Claude Code

Tools-first, IAM-mapped MCP server design for exposing pgconsole's
governed Postgres access to external AI agents.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 24, 2026 15:41
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pgconsole Ready Ready Preview, Comment Jun 24, 2026 6:03pm

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a remote Model Context Protocol (MCP) HTTP endpoint to pgconsole so external agents can use the existing governed Postgres access model (IAM + SQL permission detection + audit logging) without requiring raw connection strings.

Changes:

  • Introduces POST /mcp Streamable HTTP MCP server with token → user identity mapping, permission-filtered tool listing, and audited SQL execution.
  • Extends SQL permission analysis to return per-statement primaryPermissions, used to enforce “tool permission must match statement kind-permission”.
  • Adds MCP token config parsing and audit log fields (source, tool), plus docs and unit tests.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/sql-permissions.test.ts Adds unit tests for new primaryPermissions output.
tests/mcp.test.ts Adds unit tests for MCP token parsing and per-token tool selection.
server/services/query-service.ts Refactors connection-detail building to use shared helper.
server/mcp.ts Implements MCP router, tool definitions, permission enforcement, and auditing.
server/lib/sql-permissions.ts Adds primaryPermissions to the SQL analysis result.
server/lib/db.ts Adds buildConnectionDetails() shared helper.
server/lib/config.ts Adds [[mcp.tokens]] parsing and token→email resolver.
server/lib/audit.ts Extends SQL audit events with optional MCP origin fields.
server/index.ts Mounts the MCP router on the Express app.
package.json Adds @modelcontextprotocol/sdk dependency.
pnpm-lock.yaml Locks new MCP SDK dependency tree.
plans/mcp-server.md Documents implementation plan and governance rules for MCP server.
docs/features/mcp-server.mdx Adds public documentation for MCP endpoint, config, tools, and enforcement.
docs/features/audit-log.mdx Documents new MCP audit fields.
docs/docs.json Adds MCP Server page to docs nav.
docs/configuration/config.mdx Documents [[mcp.tokens]] configuration reference.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/mcp.ts Outdated
Comment thread docs/features/mcp-server.mdx Outdated
Comment thread docs/features/mcp-server.mdx Outdated
Comment thread server/mcp.ts Outdated
Comment thread server/mcp.ts
@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a remote MCP server to pgconsole, exposing governed Postgres access to external AI agents (Claude Code, Cursor, etc.) over Streamable HTTP. Bearer tokens map to user emails, inheriting the existing IAM permission model — including per-statement SQL enforcement and audit logging — so agents operate within the same access boundaries as UI users.

  • server/mcp.ts: New 563-line Express router that creates a stateless MCP Server per request, filters the tool list by the token's union permissions, and runs discovery + execution tools with detectRequiredPermissions enforcement and auditSQL tagging.
  • server/lib/sql-permissions.ts: New primaryPermissions field tracks each statement's kind-level permission separately, enabling the per-tool "no smuggling a DROP through query" check.
  • server/lib/db.ts / server/services/query-service.ts: buildConnectionDetails is extracted to db.ts and reused by both the MCP server and the existing query service.

Confidence Score: 3/5

The core governance wiring is sound, but the multi-statement BEGIN/COMMIT path in execute may return row count 0 to the agent regardless of actual rows affected, creating a real risk of agent-driven duplicate writes on any multi-statement DML batch.

The auth, IAM enforcement, and per-statement SQL analysis layers all look correct. The primary concern is execute's transaction-wrapping logic: when statementCount > 1, finalSql is wrapped in BEGIN;…COMMIT; and passed to client.unsafe(). postgres.js populates .count from the last CommandComplete message, which for a COMMIT is zero — so result.count ?? result.rows.length resolves to 0 for every wrapped batch. An agent interpreting rowCount: 0 as 'nothing happened' may retry, producing duplicate inserts or updates. This path has no integration-test coverage in the PR.

server/mcp.ts — specifically the execute function's finalSql wrapping (lines 1287–1296) and the runStatement result extraction; and server/lib/config.ts for the token comparison.

Security Review

  • Token comparison not constant-time (server/lib/config.ts, resolveMcpTokenEmail): bearer tokens are compared with JavaScript ===, which is not constant-time and theoretically exploitable via timing side-channel.
  • No additional security issues were identified. The IAM enforcement is applied on every tool call (not just at tools/list time), the per-statement primary-permission check correctly blocks cross-tool smuggling (e.g. DROP through query), and tokens are not logged or reflected in error responses.

Important Files Changed

Filename Overview
server/mcp.ts New 563-line MCP server: token auth, per-token tool filtering, discovery tools, and execution tools with IAM enforcement. Multi-statement BEGIN/COMMIT wrapping has a rowCount correctness issue; async close handlers are fire-and-forget; reqStr returns un-trimmed values.
server/lib/config.ts Adds MCPConfig/MCPTokenConfig types, TOML parsing with duplicate-detection and email validation, and getMcpTokens/resolveMcpTokenEmail accessors. Token comparison in resolveMcpTokenEmail uses non-constant-time ===.
server/lib/sql-permissions.ts Adds primaryPermissions: Permission[] field to SqlAnalysis — tracks each statement's kind-level permission separately from function-derived permissions. Clean, well-tested change.
server/lib/db.ts Extracts buildConnectionDetails helper from query-service.ts into db.ts for reuse by the MCP server. Safe refactor with no behaviour change.
server/lib/audit.ts Extends SQLExecuteEvent with optional source and tool fields; adds an opts parameter to auditSQL so MCP calls are tagged. Backward-compatible change.
server/index.ts Mounts the new mcpRouter before connectRouter. Minimal change; correct placement.
tests/mcp.test.ts New unit tests cover config parsing, token resolution, and selectToolNames filtering. No integration tests for actual tool execution (multi-statement rowCount, BEGIN/COMMIT wrapping) — the correctness of execute under multi-statement input is untested.
tests/sql-permissions.test.ts Adds three focused tests for primaryPermissions: multi-statement order, function-derived permission exclusion, and empty SQL. All correct.
server/services/query-service.ts Replaces the inline connection-detail construction with the new buildConnectionDetails helper. Behaviour is identical; safe mechanical refactor.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Agent as AI Agent
    participant MCP as mcpRouter (POST /mcp)
    participant Config as resolveMcpTokenEmail
    participant IAM as getUserPermissions
    participant SQLCheck as detectRequiredPermissions
    participant DB as withConnection / postgres.js
    participant Audit as auditSQL

    Agent->>MCP: POST /mcp Authorization: Bearer token
    MCP->>Config: resolveMcpTokenEmail(token)
    Config-->>MCP: email (or undefined → 401)

    alt tools/list
        MCP->>IAM: getUserPermissions(email, connId) x all connections
        IAM-->>MCP: union of permissions
        MCP-->>Agent: filtered tool list
    else tools/call (execution tool)
        MCP->>IAM: requirePermission(user, connId, expectedPerm)
        IAM-->>MCP: ok or ConnectError
        MCP->>SQLCheck: detectRequiredPermissions(sql)
        SQLCheck-->>MCP: primaryPermissions + permissions
        MCP->>MCP: primaryPermissions check (anti-smuggling)
        MCP->>IAM: requirePermissions(user, connId, fullPermSet)
        IAM-->>MCP: ok or ConnectError
        MCP->>DB: client.unsafe(sql)
        DB-->>MCP: result
        MCP->>Audit: "auditSQL source=mcp tool=name"
        MCP-->>Agent: tool result (rows / rowCount)
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Agent as AI Agent
    participant MCP as mcpRouter (POST /mcp)
    participant Config as resolveMcpTokenEmail
    participant IAM as getUserPermissions
    participant SQLCheck as detectRequiredPermissions
    participant DB as withConnection / postgres.js
    participant Audit as auditSQL

    Agent->>MCP: POST /mcp Authorization: Bearer token
    MCP->>Config: resolveMcpTokenEmail(token)
    Config-->>MCP: email (or undefined → 401)

    alt tools/list
        MCP->>IAM: getUserPermissions(email, connId) x all connections
        IAM-->>MCP: union of permissions
        MCP-->>Agent: filtered tool list
    else tools/call (execution tool)
        MCP->>IAM: requirePermission(user, connId, expectedPerm)
        IAM-->>MCP: ok or ConnectError
        MCP->>SQLCheck: detectRequiredPermissions(sql)
        SQLCheck-->>MCP: primaryPermissions + permissions
        MCP->>MCP: primaryPermissions check (anti-smuggling)
        MCP->>IAM: requirePermissions(user, connId, fullPermSet)
        IAM-->>MCP: ok or ConnectError
        MCP->>DB: client.unsafe(sql)
        DB-->>MCP: result
        MCP->>Audit: "auditSQL source=mcp tool=name"
        MCP-->>Agent: tool result (rows / rowCount)
    end
Loading

Comments Outside Diff (4)

  1. server/mcp.ts, line 1287-1296 (link)

    P1 Incorrect rowCount for BEGIN/COMMIT-wrapped multi-statement queries

    When statementCount > 1 && transactionSafe, finalSql is prefixed with BEGIN; and suffixed with COMMIT;. postgres.js's client.unsafe() in simple-query mode processes each statement individually; the .count property on the aggregate result reflects the last CommandComplete message — which is COMMIT (count = 0). The execute return value would then always report rowCount: 0 for any multi-statement DML or DDL batch, even if 1 000 rows were modified. An agent that trusts this count may decide the operation failed and retry, causing duplicate writes.

    The safest fix is to not wrap in a synthetic transaction and let the caller decide, or to sum individual statement counts by running each statement separately. If wrapping is kept, the actual affected-row count must be extracted from the intermediate results before the COMMIT, not from the final result object.

  2. server/lib/config.ts, line 809-811 (link)

    P2 security Bearer token comparison is not constant-time

    Array.find with t.token === token uses JavaScript's === operator, which short-circuits on the first differing byte. An attacker making many requests can distinguish "token has correct prefix" from "token is completely wrong" via response-time measurements. For a bearer-token auth gate on a database API, a constant-time comparison (e.g. crypto.timingSafeEqual) is the standard mitigation.

  3. server/mcp.ts, line 1440-1445 (link)

    P2 Async close() calls are fire-and-forget in the res.on('close') handler

    transport.close() and server.close() are both async but are called without await inside a synchronous event callback. If either rejects, Node.js surfaces an unhandled rejection. More importantly, if server.close() performs async cleanup (flushing pending handler state), it may not finish before the next request re-uses the same infrastructure. Consider wrapping in an IIFE with error logging: void (async () => { try { await transport.close(); await server.close(); } catch (e) { console.error('MCP cleanup error:', e) } })() .

  4. server/mcp.ts, line 1060-1066 (link)

    P2 reqStr validates a trimmed value but returns the raw (un-trimmed) string

    The guard !v.trim() correctly rejects blank input, but return v returns the original string including any surrounding whitespace. For the connection parameter this means buildConnectionDetails(" conn-id ") will receive a padded string that won't match any configured connection ID, producing a confusing "Connection not found" error rather than a validation error. Returning v.trim() would make the behaviour consistent with the guard.

Reviews (1): Last reviewed commit: "feat: add remote MCP server for governed..." | Re-trigger Greptile

Expose pgconsole as a remote MCP server (Streamable HTTP, mounted on the
existing Express app) so external AI agents can operate on the managed
Postgres connections through the same governance the UI uses.

Identity: Authorization: Bearer <token> resolves to a user email via
[[mcp.tokens]] in pgconsole.toml; every request runs as that user, so the
existing IAM permissions apply unchanged. Read-only by default — write/ddl
are opt-in per the token's granted permissions.

Tools (filtered per token by IAM permission):
- discovery: list_connections, list_objects (paginated/filterable),
  describe_table
- execution: explain_query (explain), query (read), write_data (write),
  run_ddl (ddl)

Enforcement reuses detectRequiredPermissions + IAM: every statement's
primary kind-permission must equal the tool's permission (blocks smuggling
a DROP through query and mixed-class batches), and the full required set
(including function-derived permissions like pg_terminate_backend → admin)
must be a subset of the token's grants. Every call is audited with
source=mcp and the tool name.

Adds @modelcontextprotocol/sdk. Docs: config reference (MCP Tokens) and a
feature page; audit-log gains source/tool fields.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify

mintlify Bot commented Jun 24, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
pgconsole 🟢 Ready View Preview Jun 24, 2026, 4:20 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Replace the flat `[[mcp.tokens]]` (token → user email, inheriting that
user's *full* IAM grant) with a first-class `[[agents]]` principal, so the
two identity modes from the "identity for AI agents" delegation model are
both expressible:

- Pure agent (case 3) — a standalone service account. Not a [[users]]
  entry (no UI login, no license seat). Authorized only by IAM rules whose
  members include `agent:<id>`; `*`/`group:`/`user:` rules never apply, so
  no agent silently inherits a broad grant. Audited as `agent:<id>`.

- Delegated agent (case 2, on_behalf_of set) — acts for a user, inheriting
  getUserPermissions narrowed by optional `permissions`/`connections` caps.
  Can never exceed the user and deprovisions automatically with them
  ("persona shadowing"). Audited as the user, tagged with the agent.

Closes the prior least-privilege gap: a token previously got the user's
entire IAM grant with no way to scope it down per agent.

Implementation:
- config: `[[agents]]` parsing + `agent:` IAM member type; getAgents/
  getAgentById/getAgentByToken (replaces the MCP token getters).
- iam: getAgentPermissions for pure agents (agent:-only matching).
- mcp: a Principal class resolves effective permissions per connection and
  carries the audit identity; tool filtering + per-statement enforcement
  are unchanged, just principal-aware. The token folds onto the agent.
- audit: sql.execute gains an `agent` field; actor is the user (delegated)
  or `agent:<id>` (pure).
- docs + plan updated; tests cover agent parsing, validation, the agent:
  IAM member type, pure-agent resolution, and delegated caps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread server/mcp.ts
Comment thread server/mcp.ts
Comment thread server/mcp.ts
Comment thread server/lib/config.ts Outdated
Simplification pass on the agents feature:

- iam.ts: extract a private resolvePermissions(connectionId, matches) engine
  shared by getUserPermissions and getAgentPermissions (removes ~25 lines of
  duplicated auth/plan shortcuts + rule-iteration scaffolding).
- Move delegated-agent resolution (getUserPermissions ∩ caps + connection cap)
  out of mcp.ts's Principal and into iam.ts getAgentPermissions(agent, conn),
  so all principal permission resolution has one home. Principal shrinks to
  identity + audit actor and no longer imports getUserPermissions.
- config.ts: hoist the duplicated email regex (EMAIL_RE) and the permission-array
  parsing (`*` expansion + validation) into a shared parsePermissionList helper
  used by both the agent-cap and IAM-rule loops; drop the third copy of the
  permission whitelist.

No behavior change — verified live (pure + delegated agents, permission/connection
caps still enforced) and 468 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread server/lib/config.ts Outdated
Comment thread server/mcp.ts
From Copilot review (PR #18). All four findings were valid:

- Transaction wrap dropped the user's last statement into COMMIT when the
  batch lacked a trailing semicolon: `BEGIN;\n<sql>\nCOMMIT;` →
  `INSERT b\nCOMMIT` → syntax error → whole batch rolled back. Use
  `\n;\n` before COMMIT so the last statement is terminated even when it
  ends in a line comment (a bare `;` would be swallowed by the comment).
  Fixed in both the MCP path and the original query-service RPC path
  (same pre-existing bug). Verified live: multi-INSERT without trailing
  `;` now commits.
- reqStr now returns the trimmed value (whitespace-padded connection/
  schema/table/sql no longer fail validation yet mismatch lookups).
- optStr trims and treats all-whitespace as "not provided".
- Agent `connections` cap trims each id before validating/storing, matching
  how IAM already trims connection references.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread server/lib/iam.ts
Comment thread plans/mcp-server.md
From Copilot review (PR #18):
- getAgentPermissions docstring now notes the shared auth-disabled /
  IAM-not-licensed full-access short-circuit (the uniform plan-gating
  posture applied to every principal, same as getUserPermissions), so it
  no longer reads as if pure agents are always limited to agent: rules.
- plans/mcp-server.md: reference detectRequiredPermissions by name instead
  of a hard-coded sql-permissions.ts line number that had already shifted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread server/mcp.ts
Comment thread server/lib/iam.ts
From Copilot review (PR #18), both valid:
- Parse the Authorization scheme case-insensitively (RFC 6750) so clients
  sending `bearer <token>` aren't rejected. Verified live.
- getUserPermissions resolved the user's groups eagerly even when
  resolvePermissions short-circuits to ALL_PERMISSIONS (auth off / IAM
  unlicensed) — a small regression from the resolver refactor. Compute
  groupIds lazily, only when a group: member is actually evaluated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread server/services/query-service.ts
Comment thread server/mcp.ts
Comment thread server/mcp.ts
Comment thread server/mcp.ts
From Copilot review (PR #18):
- execute()/explain_query() now fail closed with "Connection not found or
  not accessible" when the agent has zero permissions on the connection,
  matching the discovery tools (was a generic "Permission denied", which
  was inconsistent). Added requireAccessible() and routed requireAny
  through it so the message has one source.
- Extract dispatchTool() (the CallTool switch) and export it, then add unit
  tests for the security-critical enforcement paths — all throw before any
  DB I/O: query rejects DROP and mixed-class batches, write_data rejects a
  SELECT, a read-capped delegated agent is denied a function-derived admin
  (pg_terminate_backend), explain_query rejects non-read/multi statements,
  inaccessible connections fail closed, and unknown tools are rejected.

(The 4th review comment — getConnectionById "unused" in query-service.ts —
is a false positive; it is still used by auditExport.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread server/mcp.ts
Comment thread server/mcp.ts
Comment thread server/mcp.ts Outdated
Comment thread docs/features/mcp-server.mdx
From Copilot review (PR #18) — the docs claimed "SELECT/SHOW" but
enforcement keyed off the `read` permission class, which is broader.

- explain_query: Postgres EXPLAIN only accepts SELECT among read-class
  statements (verified: EXPLAIN SHOW/SET/VACUUM/BEGIN are syntax errors).
  The old `primary === read` check let those through to a confusing PG
  syntax error. Now restricted to a single SELECT (new `kinds` field on
  SqlAnalysis), with the message/docs updated to match. Gives a clean,
  fast rejection.
- query: keeps the uniform permission-class enforcement (the tool *is* the
  `read` permission, consistent with write_data/run_ddl and the UI read
  path — SET/VACUUM etc. run fine and need only `read`). Docs/tool
  description updated to say "read-only statements (SELECT, SHOW, …)"
  instead of implying only SELECT/SHOW.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

@tianzhou tianzhou merged commit 4a9d24e into main Jun 24, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants