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 ```