Skip to main content

PostgreSQL (Willow-hosted)

Query your PostgreSQL database from any MCP client (Cursor, Claude Code, ChatGPT and Willow agents) through a read-only MCP server that Willow builds and runs. You don't install or host anything. If your database is on a private network, you can run the same server in your own cluster instead: see Private databases (hybrid deployments).

You set it up once: create a read-only database user, allow Willow's IP address, and paste the connection string into Willow. After that, people sign in to Willow with SSO as usual. They never see or enter database credentials, and Willow groups decide who can use the connector.

How it works​

  1. An MCP client calls a PostgreSQL tool through your organization's Willow gateway.
  2. The gateway applies your groups, policies and audit logging, then forwards the call to Willow's PostgreSQL server with your connection string.
  3. The server opens one TLS connection to your database, runs the query in a read-only transaction, and closes the connection. It keeps nothing between calls.

What it can and can't do​

  • Read-only. Only SELECT, WITH, EXPLAIN (without ANALYZE) and SHOW run. Writes, schema changes, SET, COPY, DO blocks and functions that touch files or other servers are refused before they reach your database. Every query also runs inside a read-only transaction, as a user that can only read.
  • One statement per call. Stacked statements such as SELECT 1; DROP TABLE x are refused.
  • Bounded. Queries stop after 15 seconds. Results are capped at 1,000 rows and about 1 MB.
  • Public databases, or private ones on hybrid. The Willow-hosted server only reaches databases on the public internet. If your database is on a private network and you run a hybrid deployment, run the same server in your own cluster instead. See Private databases (hybrid deployments).

Available Tools​

ToolWhat it does
list_tablesLists the tables and views the database user can see, optionally in one schema.
describe_tableLists a table's columns, with type, nullability, default and primary key.
run_selectRuns one read-only SQL statement and returns the rows.

Setup​

1. Create a read-only database user​

Connect to the database Willow should read, as an admin (on Amazon RDS, the master user), and run the script below.

Before running it:

  • Replace replace-with-a-strong-password with a strong password.
  • If your tables aren't in the public schema, repeat the "Schema: public" block for each schema.
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'willow_readonly') THEN
CREATE ROLE willow_readonly;
END IF;
END
$$;

-- CONNECTION LIMIT caps the load Willow can put on the database.
ALTER ROLE willow_readonly
LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS
CONNECTION LIMIT 3
PASSWORD 'replace-with-a-strong-password';

ALTER ROLE willow_readonly SET default_transaction_read_only = on;
ALTER ROLE willow_readonly SET statement_timeout = '15s';
ALTER ROLE willow_readonly SET lock_timeout = '2s';
ALTER ROLE willow_readonly SET idle_in_transaction_session_timeout = '30s';
ALTER ROLE willow_readonly SET work_mem = '16MB';
-- Needs a superuser (rds_superuser on RDS). Drop this line if it is refused.
ALTER ROLE willow_readonly SET temp_file_limit = '1GB';

DO $$
BEGIN
EXECUTE format('GRANT CONNECT ON DATABASE %I TO willow_readonly', current_database());
END
$$;

-- Schema: public. Repeat this block for each schema Willow should read.
GRANT USAGE ON SCHEMA public TO willow_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO willow_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO willow_readonly;

The script is safe to run again. ALTER DEFAULT PRIVILEGES only covers tables created later by the user who ran the script, so re-run the GRANT SELECT line after other users add tables.

Hide sensitive tables or columns​

A table-level SELECT covers every column. To hide columns, revoke it and grant only the columns Willow may read:

REVOKE SELECT ON public.users FROM willow_readonly;
GRANT SELECT (id, name, created_at) ON public.users TO willow_readonly;

To hide a whole table:

REVOKE SELECT ON public.api_keys FROM willow_readonly;

2. Allow Willow's IP address​

Willow connects from a fixed IP address. Allow it on port 5432 (or your database's port) in your firewall, security group, or the database's network settings:

RegionIP address
US3.130.252.122

On Amazon RDS, the database must be publicly accessible, and its security group needs an inbound rule for PostgreSQL from 3.130.252.122/32.

3. Build the connection string​

postgresql://willow_readonly:<password>@<host>:5432/<database>?sslmode=require
  • TLS is required. sslmode=require encrypts the connection. Use sslmode=verify-full if your server's certificate is signed by a public certificate authority and you also want the hostname checked.
  • URL-encode special characters in the password. For example, @ becomes %40 and / becomes %2F.
  • Only the URL form above is supported, with exactly one host.

4. Add the connector​

  1. In Willow, go to MCP Servers and add PostgreSQL from the catalog.
  2. Keep Server URL as https://willow-postgres-mcp.withwillow.ai, the server Willow runs.
  3. Switch to Advanced.
  4. Under API key, choose One shared.
  5. Paste the connection string into API key. Leave Header as Authorization and Scheme as Bearer.
  6. Finish setup. Willow stores the connection string in your organization's vault.
warning

Choose One shared, not Each person's own. With "Each person's own", every user would be asked for the database connection string.

5. Assign groups​

Give the connector to the groups that should be able to query the database. Only members of those groups, and the agents you assign, can call its tools.

6. Test it​

Ask your AI assistant:

List the tables in my PostgreSQL database.

You should see your tables. If you get an error, see Troubleshooting.

Private databases (hybrid deployments)​

If your database isn't reachable from the internet (for example, an Amazon RDS instance that isn't publicly accessible), run the server next to your Willow run gateway. It's the same image with the same read-only protections. The difference is where it runs: the connection string stays in a Kubernetes secret in your cluster, the server can reach private addresses, and nothing is exposed to the internet.

This needs a hybrid deployment or a Willow Tunnel: your run gateway is what calls the server. A tunnel is the simplest option, because nothing in your network accepts inbound connections.

1. Create a read-only database user​

Run the script from step 1 above. Skip step 2: no Willow IP address needs access.

2. Run the server in your cluster​

The image is quay.io/webrix/willow-postgres-mcp. It supports linux/amd64 and linux/arm64. Pin a specific tag rather than latest, so the server only changes when you choose to upgrade it.

VariableRequiredDescription
DATABASE_URLYespostgresql://willow_readonly:<password>@<host>:5432/<database>?sslmode=require. The server checks it at startup and exits if it's malformed. sslmode=disable is allowed here, for databases without TLS on your private network.
MCP_AUTH_TOKENRecommendedA random token that callers must send as Authorization: Bearer <token>. Without it, anything that can reach the service can query the database.

Apply this in the namespace where your run gateway runs, after replacing the two secret values:

apiVersion: v1
kind: Secret
metadata:
name: willow-postgres-mcp
stringData:
DATABASE_URL: postgresql://willow_readonly:<password>@<host>:5432/<database>?sslmode=require
MCP_AUTH_TOKEN: <output of: openssl rand -hex 32>
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: willow-postgres-mcp
spec:
replicas: 1
selector:
matchLabels:
app: willow-postgres-mcp
template:
metadata:
labels:
app: willow-postgres-mcp
spec:
automountServiceAccountToken: false
containers:
- name: willow-postgres-mcp
image: quay.io/webrix/willow-postgres-mcp:<tag>
ports:
- containerPort: 3000
envFrom:
- secretRef:
name: willow-postgres-mcp
readinessProbe:
httpGet:
path: /healthz
port: 3000
livenessProbe:
httpGet:
path: /healthz
port: 3000
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 256Mi
securityContext:
runAsNonRoot: true
runAsUser: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
---
apiVersion: v1
kind: Service
metadata:
name: willow-postgres-mcp
spec:
selector:
app: willow-postgres-mcp
ports:
- port: 3000
targetPort: 3000

Don't add an Ingress. The run gateway reaches the server inside the cluster.

3. Add the connector​

  1. In Willow, go to MCP Servers and add PostgreSQL from the catalog.

  2. Set Server URL to your server's address in the cluster, without /mcp:

    http://willow-postgres-mcp.<namespace>.svc.cluster.local:3000
  3. Switch to Advanced, choose One shared under API key, and paste the MCP_AUTH_TOKEN value. Leave Header as Authorization and Scheme as Bearer. Willow only holds this token; the database password stays in your cluster.

  4. Set this MCP server's gateway to your own run gateway, either an External Run Service or a Willow Tunnel. If that gateway isn't your organization's default, pick it on the server rather than making it the default, so your other MCP servers stay where they are.

  5. Publish the server, assign groups, and test it.

Willow discovers the server's tools by calling your run gateway. With a Willow Tunnel that call goes through the tunnel, so no inbound access is needed. With an External Run Service, if you block inbound calls from Willow, the tool list can't load in the admin console, but tool calls still work. See What You Lose Without Inbound.

To rotate the database password, update the Kubernetes secret and restart the deployment. Nothing changes in Willow.

Rotate or revoke access​

  • Rotate the password: run ALTER ROLE willow_readonly PASSWORD 'new-password';, then update the connection string in the connector's settings in Willow.
  • Revoke access immediately: run ALTER ROLE willow_readonly NOLOGIN;, or remove the connector from Willow. To remove the user completely:
REASSIGN OWNED BY willow_readonly TO postgres;
DROP OWNED BY willow_readonly;
DROP ROLE willow_readonly;

Example AI Prompts​

  • "What tables are in the public schema?"
  • "Describe the orders table."
  • "How many orders were created per day last week?"
  • "Show the 10 customers with the highest total order value."
  • "Explain the query plan for selecting orders by customer id."

Troubleshooting​

"Could not reach the database"​

Willow couldn't open a connection.

  • Check the host and port in the connection string.
  • Check that 3.130.252.122 is allowed on that port. On RDS, check the security group and that the instance is publicly accessible.

"The database host is not a public address"​

The host resolves to a private, loopback or cloud metadata address. Willow only connects to databases on the public internet.

TLS errors​

  • sslmode=disable and sslmode=allow are refused. Enable TLS on the server and use sslmode=require.
  • With sslmode=verify-full or verify-ca, the certificate must be signed by a public certificate authority. For a private authority (including the Amazon RDS CA), use sslmode=require.

"password authentication failed"​

The user or password is wrong, or the password has special characters that aren't URL-encoded.

"permission denied for table ..."​

The willow_readonly user can't read that table. Grant it with GRANT SELECT ON <schema>.<table> TO willow_readonly;, or run the script's schema block for that schema.

"The query was cancelled after 15 seconds"​

The query ran longer than 15 seconds. Add filters or a LIMIT, query a smaller time range, or add an index.

"too many connections for role"​

The user is limited to 3 connections at once. Wait a moment and retry; under steady heavy use, raise the limit with ALTER ROLE willow_readonly CONNECTION LIMIT 5;.

A query is refused​

Only one read-only statement runs per call. Writes, SET, COPY, DO blocks, EXPLAIN ANALYZE, SELECT ... FOR UPDATE, sequence functions and file or server functions are refused by design.

Security Best Practices​

  • Use a dedicated user for Willow, never an application or admin user.
  • Grant only the schemas, tables and columns Willow needs.
  • Point Willow at a read replica if you have one, to keep queries off your primary.
  • Rotate the password periodically, and immediately if you think it leaked.