diff --git a/README.md b/README.md index 1ebd7d5212..3648c0b5af 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ These servers aim to demonstrate MCP features and the TypeScript and Python SDKs - **[Google Drive](src/gdrive)** - File access and search capabilities for Google Drive - **[Google Maps](src/google-maps)** - Location services, directions, and place details - **[Memory](src/memory)** - Knowledge graph-based persistent memory system +- **[MindsDB](src/mindsdb)** - Federated data. Join data from any combination of data sources. - **[PostgreSQL](src/postgres)** - Read-only database access with schema inspection - **[Puppeteer](src/puppeteer)** - Browser automation and web scraping - **[Redis](src/redis)** - Interact with Redis key-value stores diff --git a/src/mindsdb/Dockerfile b/src/mindsdb/Dockerfile new file mode 100644 index 0000000000..59ef9caca1 --- /dev/null +++ b/src/mindsdb/Dockerfile @@ -0,0 +1,24 @@ +FROM node:22.12-alpine AS builder + +COPY src/postgres /app +COPY tsconfig.json /tsconfig.json + +WORKDIR /app + +RUN --mount=type=cache,target=/root/.npm npm install + +RUN --mount=type=cache,target=/root/.npm-production npm ci --ignore-scripts --omit-dev + +FROM node:22-alpine AS release + +COPY --from=builder /app/dist /app/dist +COPY --from=builder /app/package.json /app/package.json +COPY --from=builder /app/package-lock.json /app/package-lock.json + +ENV NODE_ENV=production + +WORKDIR /app + +RUN npm ci --ignore-scripts --omit-dev + +ENTRYPOINT ["node", "dist/index.js"] \ No newline at end of file diff --git a/src/mindsdb/README.md b/src/mindsdb/README.md new file mode 100644 index 0000000000..8e7f212773 --- /dev/null +++ b/src/mindsdb/README.md @@ -0,0 +1,121 @@ +# MindsDB MCP Server + +A Model Context Protocol server that provides access to hundreds of data sources through MindsDB's SQL interface. This server enables LLMs to query and analyze data across various databases, data warehouses, and applications using standard SQL. + +## Features + +- **Universal SQL Interface**: Query any supported data source using standard SQL syntax +- **Cross-Database Joins**: Join data across different databases and platforms +- **Automatic Dialect Translation**: MindsDB handles the translation between SQL dialects +- **Wide Data Source Support**: + - Databases: MySQL, PostgreSQL, MongoDB, MariaDB, etc. + - Data Warehouses: Snowflake, BigQuery, Redshift, etc. + - Applications: Salesforce, HubSpot, SAP, etc. + - FileSystems: S3, GCS, Azure, GoogleDrive, etc. + - Files: CSV, JSON, Parquet, etc. + + +## Components + +### Tools + +- **query** + - Execute SQL queries against any connected data source + - Input: `sql` (string): Standard MySQL or MindsDB SQL query (MindsDB handles dialect translation to other data sources) + - MindsDB automatically handles: + - SQL dialect translation + - Query federation + - Connection management + - Data type conversions + - Cross-database operations + +### Resources + +The server provides schema information for tables across all connected data sources: + +- **Table Schemas** (`mindsdb:////`) + - JSON schema information for each table + - Includes column names and data types + - Automatically discovered from data source metadata + +## Usage Examples + +### Basic Queries +```sql +-- Query MySQL database +SELECT * FROM mysql_integration.customers; + +-- Query Postgres database +SELECT * FROM postgres_integration.orders; + +-- Cross-database join +SELECT c.name, o.order_date, o.amount +FROM mysql_integration.customers c +JOIN postgres_integration.orders o +ON c.id = o.customer_id; + +-- Query application data +SELECT * FROM salesforce_integration.leads; +``` + +## Usage with Claude Desktop + +To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`: + +### Docker + +```json +{ + "mcpServers": { + "mindsdb": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "mcp/mindsdb", + "mysql://user:pass@host:port/mindsdb"] + } + } +} +``` + +### NPX + +```json +{ + "mcpServers": { + "mindsdb": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-mindsdb", + "mysql://user:pass@host:port/mindsdb" + ] + } + } +} +``` + +## Building + +Docker: + +```sh +docker build -t mcp/mindsdb -f src/mindsdb/Dockerfile . +``` + +## Configuration + +1. Set up MindsDB instance (cloud or self-hosted) [https://docs.mindsdb.com] +2. Create integrations to your data sources in MindsDB +3. Connect the MCP server to MindsDB using the MySQL wire protocol + +## Limitations + +- Query performance depends on the underlying data sources +- Write operations require appropriate permissions on the target data sources + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/mindsdb/index.ts b/src/mindsdb/index.ts new file mode 100644 index 0000000000..a869d713df --- /dev/null +++ b/src/mindsdb/index.ts @@ -0,0 +1,142 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListResourcesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import mysql from 'mysql2/promise'; + +const server = new Server( + { + name: "example-servers/mindsdb", + version: "0.1.0", + }, + { + capabilities: { + resources: {}, + tools: {}, + }, + }, +); + +const args = process.argv.slice(2); +if (args.length === 0) { + console.error("Please provide a database URL as a command-line argument"); + process.exit(1); +} + +const databaseUrl = args[0]; + +const resourceBaseUrl = new URL(databaseUrl); +resourceBaseUrl.protocol = "mysql:"; +resourceBaseUrl.password = ""; + +const pool = mysql.createPool(databaseUrl); + +const SCHEMA_PATH = "schema"; + +server.setRequestHandler(ListResourcesRequestSchema, async () => { + const connection = await pool.getConnection(); + try { + const [rows] = await connection.query( + "SELECT * FROM information_schema.tables WHERE table_schema != 'information_schema'" + ); + return { + resources: (rows as any[]).map((row) => ({ + uri: new URL(`${row.TABLE_NAME}/${SCHEMA_PATH}`, resourceBaseUrl).href, + mimeType: "application/json", + name: `"${row.TABLE_NAME}" database schema`, + })), + }; + } finally { + connection.release(); + } +}); + +server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const resourceUrl = new URL(request.params.uri); + + const pathComponents = resourceUrl.pathname.split("/"); + const schema = pathComponents.pop(); + const tableName = pathComponents.pop(); + + if (schema !== SCHEMA_PATH) { + throw new Error("Invalid resource URI"); + } + + const connection = await pool.getConnection(); + try { + const [rows] = await connection.query( + "SELECT COLUMN_NAME as column_name, DATA_TYPE as data_type FROM information_schema.columns WHERE table_name = $1 AND table_schema = $2", + [tableName, schema] + ); + + return { + contents: [ + { + uri: request.params.uri, + mimeType: "application/json", + text: JSON.stringify(rows, null, 2), + }, + ], + }; + } finally { + connection.release(); + } +}); + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "query", + description: "Run a read-only SQL query", + inputSchema: { + type: "object", + properties: { + sql: { type: "string" }, + }, + }, + }, + ], + }; +}); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (request.params.name === "query") { + const sql = request.params.arguments?.sql as string; + + const connection = await pool.getConnection(); + try { + await connection.beginTransaction(); + // Set session to read only + await connection.query("SET TRANSACTION READ ONLY"); + const [rows] = await connection.query(sql); + return { + content: [{ type: "text", text: JSON.stringify(rows, null, 2) }], + isError: false, + }; + } catch (error) { + throw error; + } finally { + try { + await connection.rollback(); + } catch (error) { + console.warn("Could not roll back transaction:", error); + } + connection.release(); + } + } + throw new Error(`Unknown tool: ${request.params.name}`); +}); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +runServer().catch(console.error); diff --git a/src/mindsdb/package.json b/src/mindsdb/package.json new file mode 100644 index 0000000000..e8868cf660 --- /dev/null +++ b/src/mindsdb/package.json @@ -0,0 +1,30 @@ +{ + "name": "@modelcontextprotocol/server-mindsdb", + "version": "0.6.2", + "description": "MCP server for interacting with ALL databases via MindsDB", + "license": "MIT", + "author": "MindsDB, PBC (https://mindsdb.com)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/servers/issues", + "type": "module", + "bin": { + "mcp-server-mindsdb": "dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "prepare": "npm run build", + "watch": "tsc --watch" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "1.0.1", + "mysql2": "^3.10.0" + }, + "devDependencies": { + "@types/mysql": "^2.15.25", + "shx": "^0.3.4", + "typescript": "^5.6.2" + } +} \ No newline at end of file diff --git a/src/mindsdb/tsconfig.json b/src/mindsdb/tsconfig.json new file mode 100644 index 0000000000..ec5da15825 --- /dev/null +++ b/src/mindsdb/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": [ + "./**/*.ts" + ] +}