#
tokens: 11307/50000 10/11 files (page 1/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 2. Use http://codebase.md/stefanraath3/mcp-supabase?page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── examples
│   └── claude-example.md
├── LICENSE
├── mcp-docs.txt
├── mcp-typescript-readme.txt
├── package-lock.json
├── package.json
├── README.md
└── src
    ├── db.js
    ├── http-server.js
    ├── index.js
    ├── public
    │   └── index.html
    └── server.js
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Environment variables
.env
.env.local
.env.*

# Node.js
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log

# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Build directories
dist/
build/

# Dependency directories
jspm_packages/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Mac-specific files
.DS_Store

# Editor directories and files
.idea/
.vscode/
*.swp
*.swo

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Supabase MCP Server

An MCP server that connects to a Supabase PostgreSQL database, exposing table schemas as resources and providing tools for data analysis.

## Features

- Connection to Supabase PostgreSQL database
- Table schemas exposed as resources
- Read-only SQL query tools
- Prompts for common data analysis tasks

## Setup

1. Clone this repository
2. Install dependencies:
   ```
   npm install
   ```
3. Copy `.env.example` to `.env` and update with your Supabase credentials:
   ```
   cp .env.example .env
   ```
4. Edit the `.env` file with your actual Supabase connection details

## Running the Server

### Using stdio (command line integration)

```
npm start
```

### Using HTTP with SSE (for web integration)

```
npm run start:http
```

## Using with MCP Clients

This server can be used with any MCP-compatible client, including Claude.app and the MCP Inspector for testing.

### Available Resources

- `schema://tables` - Lists all tables in the database
- `schema://table/{tableName}` - Shows schema for a specific table

### Available Tools

- `query` - Runs a read-only SQL query against the database
- `analyze-table` - Gets basic statistics about a table
- `find-related-tables` - Discovers tables related to a given table

### Available Prompts

- `table-exploration` - Guides exploration of a specific table
- `data-summary` - Creates a summary of data in a table
- `relationship-analysis` - Analyzes relationships between tables

```

--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------

```javascript
const server = require("./server");
const {
  StdioServerTransport,
} = require("@modelcontextprotocol/sdk/server/stdio.js");

async function main() {
  try {
    console.error("Starting Supabase MCP Server with stdio transport...");

    // Create stdio transport
    const transport = new StdioServerTransport();

    // Connect server to transport
    await server.connect(transport);

    console.error("Server connected and ready. Awaiting messages...");
  } catch (error) {
    console.error("Error starting server:", error);
    process.exit(1);
  }
}

main();

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "supabase-mcp-server",
  "version": "1.0.0",
  "description": "MCP server that connects to Supabase PostgreSQL database",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "start:http": "node src/http-server.js",
    "dev": "nodemon src/index.js",
    "dev:http": "nodemon src/http-server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "mcp",
    "supabase",
    "postgresql",
    "llm",
    "ai"
  ],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.6.1",
    "body-parser": "^1.20.2",
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "pg": "^8.11.3",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "nodemon": "^3.0.2"
  }
}

```

--------------------------------------------------------------------------------
/examples/claude-example.md:
--------------------------------------------------------------------------------

```markdown
# Using the Supabase MCP Server with Claude

This guide shows how to use your Supabase MCP Server with Claude through Claude.app.

## Prerequisites

1. The Supabase MCP Server running
2. A Claude.app account with API access
3. Your database configuration properly set up

## Steps to Connect

1. Start your MCP server using one of the following methods:

   **For command-line integration (stdio):**

   ```bash
   npm start
   ```

   **For HTTP/SSE (recommended for Claude.app):**

   ```bash
   npm run start:http
   ```

2. Once your server is running with HTTP/SSE on port 3000, you can access it at:

   ```
   http://localhost:3000
   ```

3. In Claude.app, use the "Connect to MCP Server" feature (when available) to connect to your local server.

## Example Prompts for Claude

Once connected to your MCP server, you can use prompts like these with Claude:

### Exploring Database Structure

```
Please use the connected MCP server to:
1. List all tables in the database
2. Show me the schema for the "users" table (or any table you identify in step 1)
3. Identify any relationships between tables
```

### Running SQL Queries

```
Using the MCP server, please:
1. Show me the schema for the "products" table
2. Run a query to get the top 5 most expensive products
3. Find any tables related to the products table
4. Explain the relationships you found
```

### Data Analysis

```
I'd like to analyze the "orders" table in our database. Using the MCP server:
1. Get basic statistics about the table
2. Find the most recent orders
3. Calculate the average order value
4. Identify any trends or patterns in the data
```

### Using Prompts

```
Please use the "data-summary" prompt with the table name "customers" to provide a comprehensive overview of our customer data.
```

## Tips for Effective Use

1. **Start with exploration**: First ask Claude to list tables and explore schemas before running specific queries.

2. **Use read-only queries**: Remember that the MCP server only allows read-only queries for security reasons.

3. **Leverage relationships**: Ask Claude to find and explain relationships between tables to better understand the database structure.

4. **Use the built-in prompts**: The MCP server includes specialized prompts for table exploration, data summaries, and relationship analysis.

5. **Combine with Claude's capabilities**: Claude can not only retrieve data but also analyze it, visualize it (through descriptions), and make recommendations based on the findings.

## Troubleshooting

If you encounter issues:

1. Check that your server is running and accessible
2. Verify your database connection credentials in the `.env` file
3. Ensure you have the correct permissions for your Supabase database
4. Check the server logs for any error messages

## Next Steps

- Customize the server by adding more specialized tools or prompts
- Deploy the server to a public endpoint for remote access
- Integrate with other MCP clients besides Claude

```

--------------------------------------------------------------------------------
/src/http-server.js:
--------------------------------------------------------------------------------

```javascript
// Load environment variables from .env file
require("dotenv").config();

const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const server = require("./server");
const {
  SSEServerTransport,
} = require("@modelcontextprotocol/sdk/server/sse.js");

// Use a simple implementation that avoids the complex SSE logic
async function startHttpServer(port = 3000) {
  // Test database connection before starting server
  try {
    console.log("Initializing database connection...");
    const db = require("./db");
    const result = await db.query("SELECT NOW()");
    console.log("Database connection successful at", result.rows[0].now);
  } catch (error) {
    console.error("Failed to connect to database:", error);
    console.log(
      "Starting server anyway, but database functionality will be limited"
    );
  }

  // Create Express app
  const app = express();

  // Enable CORS
  app.use(cors());

  // Use JSON parser for all non-webhook routes
  app.use(bodyParser.json());

  // Serve static files from the public directory
  app.use(express.static("public"));

  // Simple health check endpoint
  app.get("/health", (req, res) => {
    res.json({ status: "ok", message: "MCP Server is running" });
  });

  // Alternative approach to test basic database connectivity
  app.get("/test-db", async (req, res) => {
    try {
      // Include connection parameters in response (without password)
      const connectionParams = {
        host: process.env.PGHOST,
        port: process.env.PGPORT,
        database: process.env.PGDATABASE,
        user: process.env.PGUSER,
      };

      console.log("Testing database connection with params:", connectionParams);

      const db = require("./db");
      const result = await db.query("SELECT NOW()");
      const tables = await db.listTables();

      res.json({
        status: "ok",
        message: "Database connection successful",
        timestamp: result.rows[0].now,
        connectionParams,
        tables,
      });
    } catch (error) {
      console.error("Database test failed:", error);

      res.status(500).json({
        status: "error",
        message: "Database connection failed",
        connectionParams: {
          host: process.env.PGHOST,
          port: process.env.PGPORT,
          database: process.env.PGDATABASE,
          user: process.env.PGUSER,
        },
        error: error.message,
        stack: error.stack,
      });
    }
  });

  // Start the server
  const httpServer = app.listen(port, () => {
    console.log(`HTTP server started on port ${port}`);
    console.log(`Health check available at http://localhost:${port}/health`);
    console.log(`Database test available at http://localhost:${port}/test-db`);
  });

  return httpServer;
}

// Start the HTTP server if this file is run directly
if (require.main === module) {
  const port = process.env.PORT || 3000;
  startHttpServer(port).catch((error) => {
    console.error("Error starting HTTP server:", error);
    process.exit(1);
  });
}

module.exports = { startHttpServer };

```

--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------

```javascript
require("dotenv").config();
const {
  McpServer,
  ResourceTemplate,
} = require("@modelcontextprotocol/sdk/server/mcp.js");
const { z } = require("zod");
const db = require("./db");

// Create MCP server instance
const server = new McpServer({
  name: process.env.SERVER_NAME || "Supabase MCP Server",
  version: process.env.SERVER_VERSION || "1.0.0",
});

// RESOURCES

// Resource to list all tables in the database
server.resource("tables-list", "schema://tables", async (uri) => {
  try {
    const tables = await db.listTables();
    return {
      contents: [
        {
          uri: uri.href,
          text: `Database Tables:\n\n${tables
            .map((table) => `- ${table}`)
            .join("\n")}`,
        },
      ],
    };
  } catch (error) {
    return {
      contents: [
        {
          uri: uri.href,
          text: `Error retrieving tables: ${error.message}`,
        },
      ],
      isError: true,
    };
  }
});

// Resource to get schema for a specific table
server.resource(
  "table-schema",
  new ResourceTemplate("schema://table/{tableName}", {
    list: "schema://tables",
  }),
  async (uri, { tableName }) => {
    try {
      const schema = await db.getTableSchema(tableName);

      // Format the schema information in a readable way
      const columnsText = schema.columns
        .map(
          (col) =>
            `- ${col.column_name} (${col.data_type})${
              col.is_nullable === "YES" ? " NULL" : " NOT NULL"
            }${
              schema.primaryKeys.includes(col.column_name) ? " PRIMARY KEY" : ""
            }`
        )
        .join("\n");

      const fkText =
        schema.foreignKeys.length > 0
          ? `\nForeign Keys:\n${schema.foreignKeys
              .map(
                (fk) =>
                  `- ${fk.column_name} -> ${fk.foreign_table_name}.${fk.foreign_column_name}`
              )
              .join("\n")}`
          : "\nNo Foreign Keys";

      const indicesText =
        schema.indices.length > 0
          ? `\nIndices:\n${schema.indices
              .map((idx) => `- ${idx.indexname}: ${idx.indexdef}`)
              .join("\n")}`
          : "\nNo Indices";

      return {
        contents: [
          {
            uri: uri.href,
            text: `Table: ${tableName}\n\nColumns:\n${columnsText}${fkText}${indicesText}`,
          },
        ],
      };
    } catch (error) {
      return {
        contents: [
          {
            uri: uri.href,
            text: `Error retrieving schema for table ${tableName}: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }
);

// TOOLS

// Tool to run a read-only SQL query
server.tool("query", { sql: z.string() }, async ({ sql }) => {
  try {
    const result = await db.executeReadOnlyQuery(sql);

    // Format the result as a table
    let formattedResult = "";

    if (result.rows.length > 0) {
      // Get column names from the first row
      const columns = Object.keys(result.rows[0]);

      // Create header row with column names
      formattedResult += columns.join(" | ") + "\n";
      formattedResult += columns.map(() => "---").join(" | ") + "\n";

      // Add data rows
      result.rows.forEach((row) => {
        formattedResult +=
          columns
            .map((col) => {
              const value = row[col];
              return value === null ? "NULL" : String(value);
            })
            .join(" | ") + "\n";
      });

      formattedResult += `\n${result.rows.length} rows returned`;
    } else {
      formattedResult = "Query returned no results";
    }

    return {
      content: [
        {
          type: "text",
          text: formattedResult,
        },
      ],
    };
  } catch (error) {
    return {
      content: [
        {
          type: "text",
          text: `Error executing query: ${error.message}`,
        },
      ],
      isError: true,
    };
  }
});

// Tool to analyze a table and get basic statistics
server.tool(
  "analyze-table",
  { tableName: z.string() },
  async ({ tableName }) => {
    try {
      const stats = await db.analyzeTable(tableName);

      // Format the statistics as text
      let formattedStats = `Table: ${tableName}\n\n`;
      formattedStats += `Row Count: ${stats.rowCount}\n\n`;
      formattedStats += "Column Statistics:\n";

      // Header
      formattedStats += "Column | Type | Null Count | Null %\n";
      formattedStats += "--- | --- | --- | ---\n";

      // Rows
      stats.columnStats.forEach((col) => {
        formattedStats += `${col.column_name} | ${col.data_type} | ${col.null_count} | ${col.null_percentage}\n`;
      });

      return {
        content: [
          {
            type: "text",
            text: formattedStats,
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error analyzing table ${tableName}: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }
);

// Tool to find related tables
server.tool(
  "find-related-tables",
  { tableName: z.string() },
  async ({ tableName }) => {
    try {
      const relatedTables = await db.findRelatedTables(tableName);

      let formattedRelationships = `Relationships for Table: ${tableName}\n\n`;

      if (relatedTables.relationships.length === 0) {
        formattedRelationships += "No relationships found";
      } else {
        // Group by relationship type for better organization
        const outgoing = relatedTables.relationships.filter(
          (r) => r.relationship_type === "outgoing"
        );
        const incoming = relatedTables.relationships.filter(
          (r) => r.relationship_type === "incoming"
        );

        if (outgoing.length > 0) {
          formattedRelationships +=
            "Outgoing Relationships (Tables this table references):\n";
          outgoing.forEach((rel) => {
            formattedRelationships += `- ${tableName}.${rel.from_column} -> ${rel.related_table}.${rel.to_column}\n`;
          });
          formattedRelationships += "\n";
        }

        if (incoming.length > 0) {
          formattedRelationships +=
            "Incoming Relationships (Tables that reference this table):\n";
          incoming.forEach((rel) => {
            formattedRelationships += `- ${rel.related_table}.${rel.from_column} -> ${tableName}.${rel.to_column}\n`;
          });
        }
      }

      return {
        content: [
          {
            type: "text",
            text: formattedRelationships,
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error finding related tables for ${tableName}: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }
);

// PROMPTS

// Prompt for table exploration
server.prompt(
  "table-exploration",
  { tableName: z.string() },
  ({ tableName }) => ({
    messages: [
      {
        role: "user",
        content: [
          {
            type: "text",
            text: `I want to explore the "${tableName}" table in our database. Please help me understand:

1. What is the schema of this table (column names, types, constraints)?
2. What relationships does this table have with other tables?
3. Can you provide some basic statistics about the data in this table?
4. What are some useful queries I could run to explore this table further?

Please use the available tools to gather this information and present it in a well-organized way.`,
          },
        ],
      },
    ],
  })
);

// Prompt for data summary
server.prompt(
  "data-summary",
  {
    tableName: z.string(),
    limit: z.number().optional(),
  },
  ({ tableName, limit = 10 }) => ({
    messages: [
      {
        role: "user",
        content: [
          {
            type: "text",
            text: `I need a summary of the data in the "${tableName}" table. Please:

1. Get the table schema to understand what we're working with
2. Analyze the table for basic stats (row count, null values)
3. Run appropriate queries to show me a sample of ${limit} records
4. Identify any potential data quality issues
5. Suggest any insights or patterns that might be useful for further analysis

Please organize your findings in a clear and concise way.`,
          },
        ],
      },
    ],
  })
);

// Prompt for relationship analysis
server.prompt(
  "relationship-analysis",
  { tableName: z.string() },
  ({ tableName }) => ({
    messages: [
      {
        role: "user",
        content: [
          {
            type: "text",
            text: `I need to understand how the "${tableName}" table relates to other tables in our database. Please:

1. Identify all tables that have foreign key relationships with this table
2. Show both incoming and outgoing relationships
3. Explain what these relationships mean in a business context
4. Provide example join queries to demonstrate how to use these relationships
5. Suggest how these relationships could be used for data analysis

Please use the appropriate tools to gather this information and present it in a clear, organized way.`,
          },
        ],
      },
    ],
  })
);

module.exports = server;

```

--------------------------------------------------------------------------------
/src/db.js:
--------------------------------------------------------------------------------

```javascript
// Load environment variables from .env file
require("dotenv").config();

const { Pool } = require("pg");

// Use the direct connection string from environment variables
const connectionString =
  process.env.DB_CONNECTION_STRING ||
  `postgresql://${process.env.PGUSER}:${process.env.PGPASSWORD}@${process.env.PGHOST}:${process.env.PGPORT}/${process.env.PGDATABASE}`;

// Log connection attempt (without the password)
const sanitizedConnectionString = connectionString.replace(/:[^:@]+@/, ":***@");
console.log(
  `Attempting to connect to PostgreSQL with: ${sanitizedConnectionString}`
);

// Create a connection pool using the connection string
const pool = new Pool({
  connectionString,
  ssl: {
    rejectUnauthorized: false, // Required for Supabase's self-signed certificates
  },
  // Transaction pooler doesn't support prepared statements
  // Important: disable prepared statements for Supabase pooler
  statement_timeout: 10000, // 10 second statement timeout
  connectionTimeoutMillis: 15000, // 15 second connection timeout
  query_timeout: 15000, // 15 second query timeout
  max: 5, // Maximum number of clients in the pool
});

// Add error handler to the pool
pool.on("error", (err) => {
  console.error("Unexpected error on idle client", err);
});

// Test the connection when the module is loaded
pool.query("SELECT NOW()", (err, res) => {
  if (err) {
    console.error("Database connection failed:", err.stack);
  } else {
    console.log("Database connected:", res.rows[0].now);
  }
});

// Helper function to execute queries with proper error handling
async function query(text, params = [], retries = 3) {
  let lastError;

  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      // For transaction pooler, we need to use a specific query format
      const queryConfig = {
        text: text,
        values: params,
        // Disable prepared statements (critical for transaction pooler compatibility)
        name: "",
      };

      const result = await pool.query(queryConfig);
      return result;
    } catch (error) {
      lastError = error;

      // Only retry on connection errors, not on query syntax errors
      const isConnectionError =
        error.code === "ECONNREFUSED" ||
        error.code === "ETIMEDOUT" ||
        error.code === "57P01" || // database admin shutdown
        error.code === "57P02" || // crash shutdown
        error.code === "57P03" || // cannot connect now
        error.message.includes("Connection terminated") ||
        error.message.includes("connection to server was lost");

      if (!isConnectionError || attempt === retries) {
        console.error(
          `Query error (attempt ${attempt}/${retries}):`,
          error.message
        );
        throw error;
      }

      // Wait before retrying (exponential backoff)
      const delay = Math.min(100 * Math.pow(2, attempt - 1), 1000);
      console.log(
        `Connection issue, retrying in ${delay}ms (attempt ${attempt}/${retries})...`
      );
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }

  throw lastError;
}

// Function to list all tables in the database
async function listTables() {
  const sql = `
    SELECT tablename 
    FROM pg_catalog.pg_tables 
    WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
    ORDER BY tablename;
  `;

  const result = await query(sql);
  return result.rows.map((row) => row.tablename);
}

// Function to get schema details for a specific table
async function getTableSchema(tableName) {
  // Get column information
  const columnsSql = `
    SELECT 
      column_name, 
      data_type, 
      is_nullable,
      column_default
    FROM 
      information_schema.columns
    WHERE 
      table_name = $1
    ORDER BY 
      ordinal_position;
  `;

  // Get primary key information
  const pkSql = `
    SELECT
      kcu.column_name
    FROM
      information_schema.table_constraints tc
      JOIN information_schema.key_column_usage kcu
        ON tc.constraint_name = kcu.constraint_name
    WHERE
      tc.constraint_type = 'PRIMARY KEY'
      AND tc.table_name = $1;
  `;

  // Get foreign key information
  const fkSql = `
    SELECT
      kcu.column_name,
      ccu.table_name AS foreign_table_name,
      ccu.column_name AS foreign_column_name
    FROM
      information_schema.table_constraints tc
      JOIN information_schema.key_column_usage kcu
        ON tc.constraint_name = kcu.constraint_name
      JOIN information_schema.constraint_column_usage ccu
        ON tc.constraint_name = ccu.constraint_name
    WHERE
      tc.constraint_type = 'FOREIGN KEY'
      AND tc.table_name = $1;
  `;

  // Get indices information
  const indicesSql = `
    SELECT
      indexname,
      indexdef
    FROM
      pg_indexes
    WHERE
      tablename = $1;
  `;

  try {
    const [columnsResult, pkResult, fkResult, indicesResult] =
      await Promise.all([
        query(columnsSql, [tableName]),
        query(pkSql, [tableName]),
        query(fkSql, [tableName]),
        query(indicesSql, [tableName]),
      ]);

    return {
      tableName,
      columns: columnsResult.rows,
      primaryKeys: pkResult.rows.map((row) => row.column_name),
      foreignKeys: fkResult.rows,
      indices: indicesResult.rows,
    };
  } catch (error) {
    console.error(
      `Error fetching schema for table ${tableName}:`,
      error.message
    );
    throw error;
  }
}

// Function to get basic statistics about a table
async function analyzeTable(tableName) {
  // Get row count
  const countSql = `SELECT COUNT(*) as count FROM "${tableName}";`;

  // Get column statistics
  const statsSql = `
    SELECT
      column_name,
      data_type,
      (
        SELECT COUNT(*)
        FROM "${tableName}"
        WHERE "${column_name}" IS NULL
      ) AS null_count
    FROM
      information_schema.columns
    WHERE
      table_name = $1;
  `;

  try {
    const [countResult, statsResult] = await Promise.all([
      query(countSql),
      query(statsSql, [tableName]),
    ]);

    const totalRows = parseInt(countResult.rows[0].count);

    // Add null percentage to each column stat
    const columnStats = statsResult.rows.map((col) => ({
      ...col,
      null_count: parseInt(col.null_count),
      null_percentage:
        totalRows > 0
          ? ((parseInt(col.null_count) / totalRows) * 100).toFixed(2) + "%"
          : "0%",
    }));

    return {
      tableName,
      rowCount: totalRows,
      columnStats,
    };
  } catch (error) {
    console.error(`Error analyzing table ${tableName}:`, error.message);
    throw error;
  }
}

// Function to find tables related to a given table (through foreign keys)
async function findRelatedTables(tableName) {
  // Tables that this table references (outgoing foreign keys)
  const referencedTablesSql = `
    SELECT
      ccu.table_name AS related_table,
      kcu.column_name AS from_column,
      ccu.column_name AS to_column,
      'outgoing' AS relationship_type
    FROM
      information_schema.table_constraints tc
      JOIN information_schema.key_column_usage kcu
        ON tc.constraint_name = kcu.constraint_name
      JOIN information_schema.constraint_column_usage ccu
        ON tc.constraint_name = ccu.constraint_name
    WHERE
      tc.constraint_type = 'FOREIGN KEY'
      AND tc.table_name = $1;
  `;

  // Tables that reference this table (incoming foreign keys)
  const referencingTablesSql = `
    SELECT
      kcu.table_name AS related_table,
      kcu.column_name AS from_column,
      ccu.column_name AS to_column,
      'incoming' AS relationship_type
    FROM
      information_schema.table_constraints tc
      JOIN information_schema.key_column_usage kcu
        ON tc.constraint_name = kcu.constraint_name
      JOIN information_schema.constraint_column_usage ccu
        ON tc.constraint_name = ccu.constraint_name
    WHERE
      tc.constraint_type = 'FOREIGN KEY'
      AND ccu.table_name = $1;
  `;

  try {
    const [referencedResult, referencingResult] = await Promise.all([
      query(referencedTablesSql, [tableName]),
      query(referencingTablesSql, [tableName]),
    ]);

    return {
      tableName,
      relationships: [...referencedResult.rows, ...referencingResult.rows],
    };
  } catch (error) {
    console.error(
      `Error finding related tables for ${tableName}:`,
      error.message
    );
    throw error;
  }
}

// Function to execute a read-only query safely
async function executeReadOnlyQuery(sql) {
  // Check if the query is attempting to modify data
  const normalizedSql = sql.trim().toLowerCase();

  if (
    normalizedSql.startsWith("insert") ||
    normalizedSql.startsWith("update") ||
    normalizedSql.startsWith("delete") ||
    normalizedSql.startsWith("drop") ||
    normalizedSql.startsWith("alter") ||
    normalizedSql.startsWith("create") ||
    normalizedSql.includes("set schema")
  ) {
    throw new Error("Only read-only queries are allowed");
  }

  try {
    // Execute with a read-only transaction to ensure safety
    await query("BEGIN TRANSACTION READ ONLY");
    const result = await query(sql);
    await query("COMMIT");
    return result;
  } catch (error) {
    await query("ROLLBACK");
    throw error;
  }
}

module.exports = {
  query,
  listTables,
  getTableSchema,
  analyzeTable,
  findRelatedTables,
  executeReadOnlyQuery,
};

```

--------------------------------------------------------------------------------
/src/public/index.html:
--------------------------------------------------------------------------------

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Supabase MCP Server Tester</title>
    <style>
      body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
        max-width: 1000px;
        margin: 0 auto;
        padding: 20px;
        line-height: 1.6;
      }

      h1,
      h2,
      h3 {
        margin-top: 1.5em;
      }

      pre {
        background-color: #f5f5f5;
        padding: 10px;
        border-radius: 5px;
        overflow-x: auto;
      }

      button {
        background-color: #4caf50;
        border: none;
        color: white;
        padding: 8px 16px;
        text-align: center;
        text-decoration: none;
        display: inline-block;
        font-size: 14px;
        margin: 4px 2px;
        cursor: pointer;
        border-radius: 4px;
      }

      input,
      select {
        padding: 8px;
        margin: 5px 0;
        border: 1px solid #ddd;
        border-radius: 4px;
      }

      .response {
        margin-top: 20px;
        border: 1px solid #ddd;
        padding: 10px;
        border-radius: 5px;
        height: 300px;
        overflow-y: auto;
      }

      .status {
        font-size: 14px;
        margin-top: 10px;
        font-style: italic;
      }

      .connected {
        color: green;
      }

      .disconnected {
        color: red;
      }
    </style>
  </head>
  <body>
    <h1>Supabase MCP Server Tester</h1>

    <div class="status disconnected" id="status">Disconnected</div>

    <h2>Connection</h2>
    <button id="connect">Connect to Server</button>
    <button id="disconnect" disabled>Disconnect</button>

    <h2>Resources</h2>
    <button id="list-tables">List Tables</button>
    <div>
      <input type="text" id="table-name" placeholder="Table name" />
      <button id="get-schema">Get Schema</button>
    </div>

    <h2>Tools</h2>
    <div>
      <textarea
        id="sql-query"
        rows="4"
        style="width: 100%"
        placeholder="SELECT * FROM your_table LIMIT 10"
      ></textarea>
      <button id="run-query">Run Query</button>
    </div>

    <div>
      <input type="text" id="analyze-table-name" placeholder="Table name" />
      <button id="analyze-table">Analyze Table</button>
    </div>

    <div>
      <input type="text" id="related-table-name" placeholder="Table name" />
      <button id="find-related">Find Related Tables</button>
    </div>

    <h2>Prompts</h2>
    <div>
      <select id="prompt-type">
        <option value="table-exploration">Table Exploration</option>
        <option value="data-summary">Data Summary</option>
        <option value="relationship-analysis">Relationship Analysis</option>
      </select>
      <input type="text" id="prompt-table-name" placeholder="Table name" />
      <input
        type="number"
        id="prompt-limit"
        placeholder="Limit (for data summary)"
        min="1"
        value="10"
      />
      <button id="use-prompt">Use Prompt</button>
    </div>

    <h2>Response</h2>
    <pre class="response" id="response"></pre>

    <script>
      let connectionId = null;
      let eventSource = null;
      let requestId = 1;

      const statusEl = document.getElementById("status");
      const responseEl = document.getElementById("response");
      const connectBtn = document.getElementById("connect");
      const disconnectBtn = document.getElementById("disconnect");

      // Connect to the server
      connectBtn.addEventListener("click", () => {
        if (eventSource) {
          eventSource.close();
        }

        responseEl.textContent = "Connecting to server...";

        eventSource = new EventSource("/sse");

        eventSource.onmessage = (event) => {
          const data = JSON.parse(event.data);

          if (data.type === "connection") {
            connectionId = data.id;
            statusEl.textContent = `Connected (ID: ${connectionId})`;
            statusEl.classList.remove("disconnected");
            statusEl.classList.add("connected");

            connectBtn.disabled = true;
            disconnectBtn.disabled = false;

            responseEl.textContent = "Connected successfully!";
          } else {
            // Handle MCP protocol messages
            responseEl.textContent = JSON.stringify(data, null, 2);
          }
        };

        eventSource.onerror = (error) => {
          console.error("SSE error:", error);
          responseEl.textContent = "Connection error: " + JSON.stringify(error);
          disconnect();
        };
      });

      // Disconnect from the server
      disconnectBtn.addEventListener("click", disconnect);

      function disconnect() {
        if (eventSource) {
          eventSource.close();
          eventSource = null;
        }

        connectionId = null;
        statusEl.textContent = "Disconnected";
        statusEl.classList.remove("connected");
        statusEl.classList.add("disconnected");

        connectBtn.disabled = false;
        disconnectBtn.disabled = true;

        responseEl.textContent = "Disconnected from server.";
      }

      // Helper to send a request to the server
      async function sendRequest(request) {
        if (!connectionId) {
          responseEl.textContent =
            "Not connected to server. Click Connect first.";
          return;
        }

        const reqId = String(requestId++);
        request.id = reqId;

        try {
          const response = await fetch(`/messages/${connectionId}`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify(request),
          });

          if (!response.ok) {
            throw new Error(`HTTP error ${response.status}`);
          }

          responseEl.textContent = "Request sent. Waiting for response...";
        } catch (error) {
          console.error("Request error:", error);
          responseEl.textContent = "Error sending request: " + error.message;
        }
      }

      // List Tables
      document.getElementById("list-tables").addEventListener("click", () => {
        sendRequest({
          jsonrpc: "2.0",
          method: "mcp.readResource",
          params: {
            uri: "schema://tables",
          },
        });
      });

      // Get Schema
      document.getElementById("get-schema").addEventListener("click", () => {
        const tableName = document.getElementById("table-name").value.trim();

        if (!tableName) {
          responseEl.textContent = "Please enter a table name.";
          return;
        }

        sendRequest({
          jsonrpc: "2.0",
          method: "mcp.readResource",
          params: {
            uri: `schema://table/${tableName}`,
          },
        });
      });

      // Run Query
      document.getElementById("run-query").addEventListener("click", () => {
        const sql = document.getElementById("sql-query").value.trim();

        if (!sql) {
          responseEl.textContent = "Please enter an SQL query.";
          return;
        }

        sendRequest({
          jsonrpc: "2.0",
          method: "mcp.callTool",
          params: {
            name: "query",
            arguments: {
              sql,
            },
          },
        });
      });

      // Analyze Table
      document.getElementById("analyze-table").addEventListener("click", () => {
        const tableName = document
          .getElementById("analyze-table-name")
          .value.trim();

        if (!tableName) {
          responseEl.textContent = "Please enter a table name.";
          return;
        }

        sendRequest({
          jsonrpc: "2.0",
          method: "mcp.callTool",
          params: {
            name: "analyze-table",
            arguments: {
              tableName,
            },
          },
        });
      });

      // Find Related Tables
      document.getElementById("find-related").addEventListener("click", () => {
        const tableName = document
          .getElementById("related-table-name")
          .value.trim();

        if (!tableName) {
          responseEl.textContent = "Please enter a table name.";
          return;
        }

        sendRequest({
          jsonrpc: "2.0",
          method: "mcp.callTool",
          params: {
            name: "find-related-tables",
            arguments: {
              tableName,
            },
          },
        });
      });

      // Use Prompt
      document.getElementById("use-prompt").addEventListener("click", () => {
        const promptType = document.getElementById("prompt-type").value;
        const tableName = document
          .getElementById("prompt-table-name")
          .value.trim();

        if (!tableName) {
          responseEl.textContent = "Please enter a table name.";
          return;
        }

        const args = { tableName };

        // Add limit for data summary
        if (promptType === "data-summary") {
          args.limit =
            parseInt(document.getElementById("prompt-limit").value) || 10;
        }

        sendRequest({
          jsonrpc: "2.0",
          method: "mcp.getPrompt",
          params: {
            name: promptType,
            arguments: args,
          },
        });
      });
    </script>
  </body>
</html>

```

--------------------------------------------------------------------------------
/mcp-typescript-readme.txt:
--------------------------------------------------------------------------------

```
MCP TypeScript SDK NPM Version MIT licensed
Table of Contents
Overview
Installation
Quickstart
What is MCP?
Core Concepts
Server
Resources
Tools
Prompts
Running Your Server
stdio
HTTP with SSE
Testing and Debugging
Examples
Echo Server
SQLite Explorer
Advanced Usage
Low-Level Server
Writing MCP Clients
Server Capabilities
Overview
The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements the full MCP specification, making it easy to:

Build MCP clients that can connect to any MCP server
Create MCP servers that expose resources, prompts and tools
Use standard transports like stdio and SSE
Handle all MCP protocol messages and lifecycle events
Installation
npm install @modelcontextprotocol/sdk
Quick Start
Let's create a simple MCP server that exposes a calculator tool and some data:

import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// Create an MCP server
const server = new McpServer({
  name: "Demo",
  version: "1.0.0"
});

// Add an addition tool
server.tool("add",
  { a: z.number(), b: z.number() },
  async ({ a, b }) => ({
    content: [{ type: "text", text: String(a + b) }]
  })
);

// Add a dynamic greeting resource
server.resource(
  "greeting",
  new ResourceTemplate("greeting://{name}", { list: undefined }),
  async (uri, { name }) => ({
    contents: [{
      uri: uri.href,
      text: `Hello, ${name}!`
    }]
  })
);

// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
What is MCP?
The Model Context Protocol (MCP) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:

Expose data through Resources (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
Provide functionality through Tools (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
Define interaction patterns through Prompts (reusable templates for LLM interactions)
And more!
Core Concepts
Server
The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:

const server = new McpServer({
  name: "My App",
  version: "1.0.0"
});
Resources
Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects:

// Static resource
server.resource(
  "config",
  "config://app",
  async (uri) => ({
    contents: [{
      uri: uri.href,
      text: "App configuration here"
    }]
  })
);

// Dynamic resource with parameters
server.resource(
  "user-profile",
  new ResourceTemplate("users://{userId}/profile", { list: undefined }),
  async (uri, { userId }) => ({
    contents: [{
      uri: uri.href,
      text: `Profile data for user ${userId}`
    }]
  })
);
Tools
Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:

// Simple tool with parameters
server.tool(
  "calculate-bmi",
  {
    weightKg: z.number(),
    heightM: z.number()
  },
  async ({ weightKg, heightM }) => ({
    content: [{
      type: "text",
      text: String(weightKg / (heightM * heightM))
    }]
  })
);

// Async tool with external API call
server.tool(
  "fetch-weather",
  { city: z.string() },
  async ({ city }) => {
    const response = await fetch(`https://api.weather.com/${city}`);
    const data = await response.text();
    return {
      content: [{ type: "text", text: data }]
    };
  }
);
Prompts
Prompts are reusable templates that help LLMs interact with your server effectively:

server.prompt(
  "review-code",
  { code: z.string() },
  ({ code }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please review this code:\n\n${code}`
      }
    }]
  })
);
Running Your Server
MCP servers in TypeScript need to be connected to a transport to communicate with clients. How you start the server depends on the choice of transport:

stdio
For command-line tools and direct integrations:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({
  name: "example-server",
  version: "1.0.0"
});

// ... set up server resources, tools, and prompts ...

const transport = new StdioServerTransport();
await server.connect(transport);
HTTP with SSE
For remote servers, start a web server with a Server-Sent Events (SSE) endpoint, and a separate endpoint for the client to send its messages to:

import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";

const server = new McpServer({
  name: "example-server",
  version: "1.0.0"
});

// ... set up server resources, tools, and prompts ...

const app = express();

app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

app.post("/messages", async (req, res) => {
  // Note: to support multiple simultaneous connections, these messages will
  // need to be routed to a specific matching transport. (This logic isn't
  // implemented here, for simplicity.)
  await transport.handlePostMessage(req, res);
});

app.listen(3001);
Testing and Debugging
To test your server, you can use the MCP Inspector. See its README for more information.

Examples
Echo Server
A simple server demonstrating resources, tools, and prompts:

import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({
  name: "Echo",
  version: "1.0.0"
});

server.resource(
  "echo",
  new ResourceTemplate("echo://{message}", { list: undefined }),
  async (uri, { message }) => ({
    contents: [{
      uri: uri.href,
      text: `Resource echo: ${message}`
    }]
  })
);

server.tool(
  "echo",
  { message: z.string() },
  async ({ message }) => ({
    content: [{ type: "text", text: `Tool echo: ${message}` }]
  })
);

server.prompt(
  "echo",
  { message: z.string() },
  ({ message }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please process this message: ${message}`
      }
    }]
  })
);
SQLite Explorer
A more complex example showing database integration:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import sqlite3 from "sqlite3";
import { promisify } from "util";
import { z } from "zod";

const server = new McpServer({
  name: "SQLite Explorer",
  version: "1.0.0"
});

// Helper to create DB connection
const getDb = () => {
  const db = new sqlite3.Database("database.db");
  return {
    all: promisify<string, any[]>(db.all.bind(db)),
    close: promisify(db.close.bind(db))
  };
};

server.resource(
  "schema",
  "schema://main",
  async (uri) => {
    const db = getDb();
    try {
      const tables = await db.all(
        "SELECT sql FROM sqlite_master WHERE type='table'"
      );
      return {
        contents: [{
          uri: uri.href,
          text: tables.map((t: {sql: string}) => t.sql).join("\n")
        }]
      };
    } finally {
      await db.close();
    }
  }
);

server.tool(
  "query",
  { sql: z.string() },
  async ({ sql }) => {
    const db = getDb();
    try {
      const results = await db.all(sql);
      return {
        content: [{
          type: "text",
          text: JSON.stringify(results, null, 2)
        }]
      };
    } catch (err: unknown) {
      const error = err as Error;
      return {
        content: [{
          type: "text",
          text: `Error: ${error.message}`
        }],
        isError: true
      };
    } finally {
      await db.close();
    }
  }
);
Advanced Usage
Low-Level Server
For more control, you can use the low-level Server class directly:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  ListPromptsRequestSchema,
  GetPromptRequestSchema
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  {
    name: "example-server",
    version: "1.0.0"
  },
  {
    capabilities: {
      prompts: {}
    }
  }
);

server.setRequestHandler(ListPromptsRequestSchema, async () => {
  return {
    prompts: [{
      name: "example-prompt",
      description: "An example prompt template",
      arguments: [{
        name: "arg1",
        description: "Example argument",
        required: true
      }]
    }]
  };
});

server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  if (request.params.name !== "example-prompt") {
    throw new Error("Unknown prompt");
  }
  return {
    description: "Example prompt",
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: "Example prompt text"
      }
    }]
  };
});

const transport = new StdioServerTransport();
await server.connect(transport);
Writing MCP Clients
The SDK provides a high-level client interface:

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "node",
  args: ["server.js"]
});

const client = new Client(
  {
    name: "example-client",
    version: "1.0.0"
  },
  {
    capabilities: {
      prompts: {},
      resources: {},
      tools: {}
    }
  }
);

await client.connect(transport);

// List prompts
const prompts = await client.listPrompts();

// Get a prompt
const prompt = await client.getPrompt("example-prompt", {
  arg1: "value"
});

// List resources
const resources = await client.listResources();

// Read a resource
const resource = await client.readResource("file:///example.txt");

// Call a tool
const result = await client.callTool({
  name: "example-tool",
  arguments: {
    arg1: "value"
  }
});
Documentation
Model Context Protocol documentation
MCP Specification
Example Servers
Contributing
```
Page 1/2FirstPrevNextLast