> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Using PostgreSQL MCP with Upsun Remote Database

> Learn how to set up an MCP server to connect your Upsun PostgreSQL database to language models like Claude or IDE like VSCode GitHub Copilot and Cursor, enabling natural language data queries without writing SQL.


export const PostMeta = ({data = {}}) => {
  const {author, date, image} = data;
  const authors = Array.isArray(author) ? author : author ? [author] : [];
  const resolveAuthor = slug => {
    const entry = AUTHOR_MAP[slug] || ({});
    const name = entry.name || slug;
    const github = entry.github || null;
    const linkedin = entry.linkedin || null;
    const url = github ? `https://github.com/${github}` : linkedin || null;
    const avatarUrl = github ? `https://github.com/${github}.png?size=64` : null;
    return {
      name,
      url,
      avatarUrl
    };
  };
  const formattedDate = date ? new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }) : null;
  if (!image && authors.length === 0 && !formattedDate) return null;
  const AUTHOR_MAP = {
    "aaron-collier": {
      "name": "Aaron Collier"
    },
    "aaron-dudenhofer": {
      "name": "Aaron Dudenhofer"
    },
    "aaron-porter": {
      "name": "Aaron Porter"
    },
    "adriaan-odendaal": {
      "name": "Adriaan Odendaal"
    },
    "ajmal": {
      "name": "Ajmal Siddiqui"
    },
    "akalipetis": {
      "name": "Antonis Kalipetis"
    },
    "alexander-varwijk": {
      "name": "Alexander Varwijk"
    },
    "alicia-bevilacqua": {
      "name": "Alicia Bevilacqua"
    },
    "amelie-deguerry": {
      "name": "Amelie Deguerry"
    },
    "anacidre": {
      "name": "Ana Cidre",
      "linkedin": "https://www.linkedin.com/in/ana-cidre"
    },
    "andoni": {
      "name": "Andoni Auzmendi"
    },
    "andrei-taranu": {
      "name": "Andrei (Alex) Taranu",
      "linkedin": "https://www.linkedin.com/in/andrei-alex-taranu/"
    },
    "andrew-baxter": {
      "name": "Andrew Baxter"
    },
    "andrew-melck": {
      "name": "Andrew Melck"
    },
    "antoine-crochet-damais": {
      "name": "Antoine Crochet Damais"
    },
    "augustin-delaporte": {
      "name": "Augustin Delaporte",
      "linkedin": "https://www.linkedin.com/in/augustindelaporte/"
    },
    "branislav-bujisic": {
      "name": "Branislav Bujisic"
    },
    "carl-smith": {
      "name": "Carl Smith"
    },
    "caroline-leroy": {
      "name": "Caroline Leroy"
    },
    "cati-mayer": {
      "name": "Cati Mayer"
    },
    "catplat": {
      "name": "C Trinkwon"
    },
    "ceelolulu": {
      "name": "Celeste van der Watt"
    },
    "chadwcarlson": {
      "name": "Chad Carlson",
      "github": "chadwcarlson",
      "linkedin": "https://www.linkedin.com/in/chadwcarlson"
    },
    "chris-ward": {
      "name": "Chris Ward"
    },
    "chris-yates": {
      "name": "Chris Yates"
    },
    "christian-sieber": {
      "name": "Christian Sieber"
    },
    "christopher-lockheardt": {
      "name": "Christopher Lockheardt"
    },
    "christopher-skene": {
      "name": "Christopher Skene"
    },
    "chuck-morgan": {
      "name": "Chuck Morgan"
    },
    "corey-dockendorf": {
      "name": "Corey Dockendorf"
    },
    "crell": {
      "name": "Crell"
    },
    "damz": {
      "name": "Damz"
    },
    "dan-morrison": {
      "name": "Dan Morrison"
    },
    "davidbonachera": {
      "name": "David Bonachera",
      "github": "davidbonachera",
      "linkedin": "https://www.linkedin.com/in/davidbonachera"
    },
    "dereliahmet1": {
      "name": "Ahmet Faruk Dereli"
    },
    "devicezero": {
      "name": "Jonas Kröger",
      "github": "devicezero",
      "linkedin": "https://www.linkedin.com/in/jonaskroeger/"
    },
    "doug-goldberg": {
      "name": "Doug Goldberg"
    },
    "duncan-naves": {
      "name": "Duncan Naves",
      "github": "duncannaves",
      "linkedin": "https://www.linkedin.com/in/duncan-naves-a94423aa"
    },
    "erika-bustamante": {
      "name": "Erika Bustamante"
    },
    "fabpot": {
      "name": "Fabien Potencier"
    },
    "flovntp": {
      "name": "Florent Huck",
      "github": "flovntp",
      "linkedin": "https://www.linkedin.com/in/florenthuck"
    },
    "fred-plais": {
      "name": "Fred Plais"
    },
    "gauthier-garnier": {
      "name": "Gauthier Garnier"
    },
    "gilzow": {
      "name": "Paul Gilzow"
    },
    "gmoigneu": {
      "name": "Guillaume Moigneu",
      "github": "gmoigneu",
      "linkedin": "https://www.linkedin.com/in/guillaumemoigneu/"
    },
    "gregqualls": {
      "name": "Greg Qualls"
    },
    "guguss": {
      "name": "Augustin Delaporte"
    },
    "haylee-millar": {
      "name": "Haylee Millar"
    },
    "ivana-kotur": {
      "name": "Ivana Kotur"
    },
    "jackrabbithanna": {
      "name": "Mark Hanna"
    },
    "jared-wright": {
      "name": "Jared Wright",
      "github": "jww-sh",
      "linkedin": "https://www.linkedin.com/in/jaredwaynewright"
    },
    "jessica-orozco": {
      "name": "Jessica Orozco"
    },
    "joey-stanford": {
      "name": "Joey Stanford"
    },
    "john-grubb": {
      "name": "John Grubb"
    },
    "jonas-kruger": {
      "name": "Jonas Kruger"
    },
    "kathryn-frazer": {
      "name": "Kathryn Frazer"
    },
    "kemiojo": {
      "name": "Kemi Elizabeth Ojogbede"
    },
    "kieronsambrook-smith": {
      "name": "Kieronsambrook Smith"
    },
    "laurent-arnoud": {
      "name": "Laurent Arnoud"
    },
    "letoya-boyne": {
      "name": "Letoya Boyne"
    },
    "lolautruche": {
      "name": "Jérôme Vieilledent"
    },
    "lyly-lepinay": {
      "name": "Lyly Lepinay"
    },
    "manauwar-alam": {
      "name": "Manauwar Alam"
    },
    "marc-antoine-porri": {
      "name": "Marc Antoine Porri"
    },
    "maria-antinkaapo": {
      "name": "Maria Antinkaapo"
    },
    "maria-de-anton": {
      "name": "Maria De Anton"
    },
    "mark-dorison": {
      "name": "Mark Dorison"
    },
    "markus-hausammann": {
      "name": "Markus Hausammann"
    },
    "mary-thomas": {
      "name": "Mary Thomas"
    },
    "mathias-bolt-lesniak": {
      "name": "Mathias Bolt Lesniak"
    },
    "mathieu-strauch": {
      "name": "Mathieu Strauch"
    },
    "matthias-van-woensel": {
      "name": "Matthias Van Woensel",
      "linkedin": "https://www.linkedin.com/in/matthias-van-woensel-267a069"
    },
    "michael-sharp": {
      "name": "Michael Sharp"
    },
    "mupsi": {
      "name": "Marine Gandy"
    },
    "natalie-harper": {
      "name": "Natalie Harper"
    },
    "ngommenginger": {
      "name": "Nicolas Gommenginger",
      "linkedin": "https://www.linkedin.com/in/nicolas-gommenginger"
    },
    "nicholas-bennison": {
      "name": "Nicholas Bennison"
    },
    "nicholas-vahalik": {
      "name": "Nicholas Vahalik"
    },
    "nick-hardiman": {
      "name": "Nick Hardiman"
    },
    "nickanderegg": {
      "name": "Nickanderegg"
    },
    "nicolas-grekas": {
      "name": "Nicolas Grekas",
      "github": "nicolas-grekas",
      "linkedin": "https://www.linkedin.com/in/nicolasgrekas/"
    },
    "niti-malwade": {
      "name": "Niti Malwade"
    },
    "opensocialteam": {
      "name": "Opensocialteam"
    },
    "ori-pekelman": {
      "name": "Ori Pekelman"
    },
    "otavio-santana": {
      "name": "Otavio Santana"
    },
    "palwandi": {
      "name": "Pawan Alwandi",
      "github": "pawpy",
      "linkedin": "https://www.linkedin.com/in/pawanalwandi"
    },
    "patrick-boest": {
      "name": "Patrick Boest"
    },
    "patrick-dawkins": {
      "name": "Patrick Dawkins",
      "github": "pjcdawkins",
      "linkedin": "https://www.linkedin.com/in/patrickdawkins"
    },
    "patrick-klima": {
      "name": "Patrick Klima"
    },
    "pjcdawkins": {
      "name": "Pjcdawkins"
    },
    "prineet-kaurbhurji": {
      "name": "Prineet Kaurbhurji"
    },
    "quentin-sinig": {
      "name": "Quentin Sinig"
    },
    "ralt": {
      "name": "Florian Margaine",
      "github": "ralt",
      "linkedin": "https://www.linkedin.com/in/florian-margaine-43971136"
    },
    "ramanathanramakrishnamurthy": {
      "name": "Ramanathanramakrishnamurthy"
    },
    "remi-lejeune": {
      "name": "Rémi Lejeune"
    },
    "ribel": {
      "name": "Taras Kruts"
    },
    "robert-douglass": {
      "name": "Robert Douglass"
    },
    "rudy-weber": {
      "name": "Rudy Weber"
    },
    "ryan-hicks": {
      "name": "Ryan Hicks"
    },
    "sabri-helal": {
      "name": "Sabri Helal"
    },
    "savannah-bergeron": {
      "name": "Savannah Bergeron"
    },
    "shannon-vettes": {
      "name": "Shannon Vettes"
    },
    "shawn-ogasawara": {
      "name": "Shawn Ogasawara",
      "linkedin": "https://www.linkedin.com/in/shawn-ogasawara-83a9a0/"
    },
    "shawna-spoor": {
      "name": "Shawna Spoor"
    },
    "shedrack-akintayo": {
      "name": "Shedrack Akintayo"
    },
    "simon-ruggier": {
      "name": "Simon Ruggier"
    },
    "sophie-van-der-kindere": {
      "name": "Sophie Van Der Kindere"
    },
    "stefanos-thampis": {
      "name": "Stefanos Thampis"
    },
    "stephen-weinberg": {
      "name": "Stephen Weinberg"
    },
    "sukhman-virk": {
      "name": "Sukhman Virk"
    },
    "sumaira-nazir": {
      "name": "Sumaira Nazir"
    },
    "sumer": {
      "name": "Sümer Cip"
    },
    "syed-raza": {
      "name": "Syed Raza"
    },
    "tamara-bacchia": {
      "name": "Tamara Bacchia"
    },
    "tara-arnold": {
      "name": "Tara Arnold"
    },
    "theosakamg": {
      "name": "Mickael Gaillard",
      "github": "theosakamg"
    },
    "thomasdiluccio": {
      "name": "Thomas di Luccio"
    },
    "tim-anderson": {
      "name": "Tim Anderson"
    },
    "tom-helmer-hansen": {
      "name": "Tom Helmer Hansen"
    },
    "tylermills": {
      "name": "Tyler Mills"
    },
    "upsun": {
      "name": "Upsun"
    },
    "veronika-tolkachova": {
      "name": "Veronika Tolkachova",
      "linkedin": "https://www.linkedin.com/in/veronika-tolkachova-169167a2"
    },
    "vince-parker": {
      "name": "Vince Parker"
    },
    "vinnie-russo": {
      "name": "Vincenzo Russo"
    },
    "vrobert78": {
      "name": "Vincent Robert",
      "github": "vrobert78",
      "linkedin": "https://www.linkedin.com/in/vincent-robert-498a883"
    },
    "yuriy-babenko": {
      "name": "Yuriy Babenko"
    },
    "yuriy-gerasimov": {
      "name": "Yuriy Gerasimov"
    }
  };
  return <div className="post-meta">
      {(authors.length > 0 || formattedDate) && <div className="post-meta-info">
          {authors.length > 0 && <div className="post-meta-authors">
              {authors.map(slug => {
    const {name, url, avatarUrl} = resolveAuthor(slug);
    const inner = <>
                    {avatarUrl && <img src={avatarUrl} alt={name} className="post-meta-avatar" />}
                    <span className="post-meta-author-name">{name}</span>
                  </>;
    return url ? <a key={slug} href={url} target="_blank" rel="noopener noreferrer" className="post-meta-author">
                    {inner}
                  </a> : <span key={slug} className="post-meta-author">{inner}</span>;
  })}
            </div>}
          {authors.length > 0 && formattedDate && <span className="post-meta-separator" aria-hidden="true">·</span>}
          {formattedDate && <span className="post-meta-date">{formattedDate}</span>}
        </div>}
      {image && <img src={image} alt="" className="post-meta-image" aria-hidden="true" />}
    </div>;
};

<PostMeta data={{ author: ["gmoigneu"], date: "2025-04-30T18:00:00+00:00", image: "/images/posts/hands-on/postgres-mcp-on-upsun/postgres-mcp-on-upsun.webp" }} />

## Introduction to Model Context Protocol (MCP)

Model Context Protocol (MCP) is an open standard that creates a bridge between large language models (LLMs) and external tools, data sources, or services. It standardizes how AI assistants can access and interact with information outside their training data, allowing them to perform actions in the real world through a consistent interface.

MCP servers act as specialized connectors that expose specific capabilities to AI assistants. These servers follow the MCP specification to provide tools and resources that LLMs can use to:

1. Access data sources (like databases, APIs, or file systems)
2. Execute functions (like running queries or performing calculations)
3. Interact with external systems (like applications or services)

<Info>
  Learn more about the MCP protocol on [the official documentation](https://modelcontextprotocol.io/introduction).
</Info>

## Why Connect Databases to LLMs via MCP?

Connecting a PostgreSQL database to an LLM through MCP offers several compelling advantages:

1. **Natural language access to data**: Non-technical team members can query complex databases using plain English instead of SQL.

2. **Controlled, secure access**: MCP servers can enforce read-only access, preventing accidental data corruption or unauthorized changes.

3. **Deeper context for AI conversations**: The AI can reference your actual data when answering questions, making responses more accurate and relevant to your specific situation.

4. **Simplified data exploration**: Quickly explore database structures and content without writing SQL queries for every investigation.

5. **Enhanced productivity**: Developers can focus on higher-level problems while the AI handles routine data retrieval and analysis tasks.

6. **Cross-platform compatibility**: The same MCP server works with multiple AI assistants (Claude, GitHub Copilot, etc.) that support the protocol.

This guide demonstrates how to connect a PostgreSQL Model Context Protocol (MCP) server to an Upsun remote database, enabling AI assistants like Claude or GitHub Copilot to interact with your database data through natural language.

<Warning>
  As always when dealing with LLM's, be wary of possible hallucinations!
</Warning>

## 1. Adding a PostgreSQL Database to Upsun

If you don't have one already, add a PostgreSQL database service on your Upsun project:

1. Edit your `.upsun/config.yaml` file to add a PostgreSQL service:

```yaml {filename=".upsun/config.yaml"} theme={null}
services:
  # The name of the service container - can be any name you choose
  postgresql:
    type: postgresql:17  # Use your preferred PostgreSQL version
```

2. Create a relationship to this service in your application configuration:

```yaml {filename=".upsun/config.yaml"} theme={null}
applications:
  myapp:
    # Other configuration...
    relationships:
      database: "postgresql:postgresql"
```

3. Push your changes to deploy the PostgreSQL service:

```bash {filename="Terminal"} theme={null}
git add .upsun/config.yaml
git commit -m "Add PostgreSQL service"
upsun push
```

In order for the MCP to retrieve information, it is up to you to populate that database with some tables and records!

## 2. Opening a Tunnel to Your Remote Database

Use the Upsun CLI to open a secure tunnel to your Upsun PostgreSQL database:

```bash {filename="Terminal"} theme={null}
upsun tunnel:open
```

This command will create SSH tunnels to all your project's services, including PostgreSQL. The output will show connection details similar to:

```bash {filename="Terminal"} theme={null}
SSH tunnel opened to postgresql at: pgsql://main:main@127.0.0.1:30000/main
```

Note the connection string details - you'll need these for the MCP configuration.

Alternatively, you can obtain PostgreSQL connection details from your environment variables:

```bash {filename="Terminal"} theme={null}
upsun ssh env | grep DATABASE
```

Look for variables like `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_USERNAME`, and `DATABASE_PASSWORD`.

<Info>
  The variable prefix (`DATABASE_`) depends on how you named the relationship to the application in `config.yaml`
</Info>

## 3. Installing the PostgreSQL MCP Server

The PostgreSQL MCP server provides a standardized way for AI tools to interact with your database.

1. Make sure you have Node.js installed on your system.
2. You don't need to install anything manually - we'll use `npx` to run the server directly.

## 4. Configuring the MCP Server in Your Editor

### For VSCode

1. Install the GitHub Copilot extension if you haven't already.
2. Create a `.vscode/mcp.json` file in your project with the following configuration:

```json {filename=".vscode/mcp.json"} theme={null}
{
  "inputs": [],
  "servers": {
    "postgres": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-postgres",
        "postgresql://main:main@127.0.0.1:30000/main"
      ]
    }
  }
}
```

Replace the connection string with your actual tunnel connection details from step 2.

### For Cursor

1. Open Cursor and go to Settings > Features > MCP.
2. Click on "+ Add New MCP Server"
3. Fill out the form:
   * Name: "postgres"
   * Type: "stdio"
   * Command: "npx"
   * Arguments: `-y @modelcontextprotocol/server-postgres postgresql://main:main@127.0.0.1:30000/main`

You can also edit the `mcp.json` settings file as below:

```json {filename="mcp.json"} theme={null}
{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-postgres",
        "postgresql://main:main@127.0.0.1:30002/main"
      ]
    }
  }
}
```

Replace the connection string with your actual tunnel connection details from step 2.

## 5. Testing the Setup

1. Make sure your Upsun tunnel is open and running. You can check the tunnel status and credentials with the following command:

```bash {filename="Terminal"} theme={null}
upsun tunnel:info -A api
postgresql:
    -
        [...]
        service: postgresql
        rel: postgresql
        scheme: pgsql
        username: main
        password: main
        port: 30000
        path: main
        [...]
        type: 'postgresql:17'
        url: 'pgsql://main:main@127.0.0.1:30000/main'
```

2. Activate the AI assistant in your editor:
   * In VSCode: Open the Copilot Chat panel and switch to "Agent mode"
   * In Cursor: Open Composer and ensure the MCP server is active
3. Try asking questions about your database:
   * "What tables are in my database?"
   * "Can you show me the schema of the users table?"
   * "How many records are in the orders table?"

<img src="https://mintcdn.com/upsun-c9761871/zF_RXmglQ7AAec_B/images/posts/hands-on/postgres-mcp-on-upsun/mcp-postgres.gif?s=2836bd7c3f129e68a47274756a2d4884" alt="Testing the MCP" width="2054" height="1536" data-path="images/posts/hands-on/postgres-mcp-on-upsun/mcp-postgres.gif" />

## Troubleshooting

* **Connection issues**: Ensure your Upsun tunnel is open and the connection string is correct
* **Permission errors**: Check that your database credentials are matching
* **MCP server not responding**: Verify the MCP server configuration and ensure the required packages are installed

## Security Considerations

* The PostgreSQL MCP server provides read-only access to your database
* Always use tunnels for secure connections to remote databases
* For sensitive data, work with non-production environments by creating development branches in Upsun

## Using Non-Production Databases

One of Upsun's key advantages is the ability to create isolated environments with cloned databases:

1. Create a development branch from your production environment:
   ```bash theme={null}
   git checkout -b test-db
   git push -u origin test-db
   ```

2. Upsun automatically creates a new environment with a complete copy of your production database

3. You can then safely connect your MCP server to this development environment:
   ```bash theme={null}
   upsun tunnel:open -e test-db
   ```

This approach allows you to work with real data structures while minimizing any risk to your production environment. Each environment in Upsun has its own isolated database, making it ideal for development and testing with AI tools.

## Additional Resources

* [Upsun PostgreSQL Documentation](https://docs.upsun.com/add-services/postgresql.html)
* [Model Context Protocol Documentation](https://modelcontextprotocol.io)
