# Directory Structure ``` ├── .dockerignore ├── .gitattributes ├── .gitignore ├── Dockerfile ├── index.ts ├── LICENSE ├── package.json ├── pnpm-lock.yaml ├── README.md ├── test_db.sqlite3 ├── tsconfig.json └── utils ├── parseArguments.ts ├── SqliteDatabase.ts ├── types.ts └── version.ts ``` # Files -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- ``` 1 | node_modules 2 | ``` -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- ``` 1 | index.ts linguist-language=TypeScript 2 | package-lock.json linguist-vendored 3 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | package-lock.json 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | lerna-debug.log* 10 | .pnpm-debug.log* 11 | 12 | # Diagnostic reports (https://nodejs.org/api/report.html) 13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | *.lcov 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Snowpack dependency directory (https://snowpack.dev/) 42 | web_modules/ 43 | 44 | # TypeScript cache 45 | *.tsbuildinfo 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional stylelint cache 54 | .stylelintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variable files 66 | .env 67 | .env.development.local 68 | .env.test.local 69 | .env.production.local 70 | .env.local 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | .parcel-cache 75 | 76 | # Serverless directories 77 | .serverless/ 78 | 79 | # FuseBox cache 80 | .fusebox/ 81 | 82 | # DynamoDB Local files 83 | .dynamodb/ 84 | 85 | # TernJS port file 86 | .tern-port 87 | 88 | # Stores VSCode versions used for testing VSCode extensions 89 | .vscode-test 90 | 91 | # yarn v2 92 | .yarn/cache 93 | .yarn/unplugged 94 | .yarn/build-state.yml 95 | .yarn/install-state.gz 96 | .pnp.* 97 | 98 | build/ 99 | 100 | gcp-oauth.keys.json 101 | .*-server-credentials.json 102 | 103 | # Byte-compiled / optimized / DLL files 104 | __pycache__/ 105 | *.py[cod] 106 | *$py.class 107 | 108 | # C extensions 109 | *.so 110 | 111 | # Distribution / packaging 112 | .Python 113 | build/ 114 | develop-eggs/ 115 | dist/ 116 | downloads/ 117 | eggs/ 118 | .eggs/ 119 | lib/ 120 | lib64/ 121 | parts/ 122 | sdist/ 123 | var/ 124 | wheels/ 125 | share/python-wheels/ 126 | *.egg-info/ 127 | .installed.cfg 128 | *.egg 129 | MANIFEST 130 | 131 | # PyInstaller 132 | # Usually these files are written by a python script from a template 133 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 134 | *.manifest 135 | *.spec 136 | 137 | # Installer logs 138 | pip-log.txt 139 | pip-delete-this-directory.txt 140 | 141 | # Unit test / coverage reports 142 | htmlcov/ 143 | .tox/ 144 | .nox/ 145 | .coverage 146 | .coverage.* 147 | .cache 148 | nosetests.xml 149 | coverage.xml 150 | *.cover 151 | *.py,cover 152 | .hypothesis/ 153 | .pytest_cache/ 154 | cover/ 155 | 156 | # Translations 157 | *.mo 158 | *.pot 159 | 160 | # Django stuff: 161 | *.log 162 | local_settings.py 163 | db.sqlite3 164 | db.sqlite3-journal 165 | 166 | # Flask stuff: 167 | instance/ 168 | .webassets-cache 169 | 170 | # Scrapy stuff: 171 | .scrapy 172 | 173 | # Sphinx documentation 174 | docs/_build/ 175 | 176 | # PyBuilder 177 | .pybuilder/ 178 | target/ 179 | 180 | # Jupyter Notebook 181 | .ipynb_checkpoints 182 | 183 | # IPython 184 | profile_default/ 185 | ipython_config.py 186 | 187 | # pyenv 188 | # For a library or package, you might want to ignore these files since the code is 189 | # intended to run in multiple environments; otherwise, check them in: 190 | # .python-version 191 | 192 | # pipenv 193 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 194 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 195 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 196 | # install all needed dependencies. 197 | #Pipfile.lock 198 | 199 | # poetry 200 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 201 | # This is especially recommended for binary packages to ensure reproducibility, and is more 202 | # commonly ignored for libraries. 203 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 204 | #poetry.lock 205 | 206 | # pdm 207 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 208 | #pdm.lock 209 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 210 | # in version control. 211 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 212 | .pdm.toml 213 | .pdm-python 214 | .pdm-build/ 215 | 216 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 217 | __pypackages__/ 218 | 219 | # Celery stuff 220 | celerybeat-schedule 221 | celerybeat.pid 222 | 223 | # SageMath parsed files 224 | *.sage.py 225 | 226 | # Environments 227 | .env 228 | .venv 229 | env/ 230 | venv/ 231 | ENV/ 232 | env.bak/ 233 | venv.bak/ 234 | 235 | # Spyder project settings 236 | .spyderproject 237 | .spyproject 238 | 239 | # Rope project settings 240 | .ropeproject 241 | 242 | # mkdocs documentation 243 | /site 244 | 245 | # mypy 246 | .mypy_cache/ 247 | .dmypy.json 248 | dmypy.json 249 | 250 | # Pyre type checker 251 | .pyre/ 252 | 253 | # pytype static type analyzer 254 | .pytype/ 255 | 256 | # Cython debug symbols 257 | cython_debug/ 258 | 259 | .DS_Store 260 | 261 | # PyCharm 262 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 263 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 264 | # and can be added to the global gitignore or merged into this file. For a more nuclear 265 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 266 | #.idea/ 267 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # SQLite MCP Server 2 | 3 | A Model Context Protocol (MCP) server implementation using TypeScript for interacting with an SQLite database. This server provides an interactive interface for executing SQL queries, managing database schemas, and synthesizing business insights—all within an extensible protocol framework. 4 | 5 | Not familiar with MCP? Check out the [What is an MCP?](#whats-an-mcp) section below. 6 | 7 | ## Features 8 | 9 | | Command | Description | Example | 10 | |---------|-------------|---------| 11 | | `list_tables` | List all tables on the SQLite database | - | 12 | | `read_query` | Execute SELECT queries on the SQLite database | `SELECT * FROM users WHERE age > 18` | 13 | 14 | ## Installation & Setup 15 | 16 | ```bash 17 | git clone https://github.com/javsanmar5/mcp-server.sqlite.git 18 | cd mcp-server.sqlite 19 | ``` 20 | 21 | Since this hasn't been published as an npm package yet, we'll focus on the Docker installation method: 22 | 23 | ### 1. Build the Docker image 24 | 25 | ```bash 26 | docker build -t mcp/sqlite . 27 | ``` 28 | 29 | ### 2. Configure your AI client 30 | 31 | Add the following to your AI client's configuration file: 32 | 33 | ```json 34 | "mcpServers": { 35 | "sqlite": { 36 | "command": "docker", 37 | "args": [ 38 | "run", 39 | "--rm", 40 | "-i", 41 | "-v", 42 | "mcp-test:/mcp", 43 | "mcp/sqlite", 44 | "--db-path", 45 | "test_db.sqlite3" 46 | ] 47 | } 48 | } 49 | ``` 50 | 51 | If you don't know what is that json file you might want to see the [Client Configuration Guide](#tutorial-setting-up-with-claude-desktop). 52 | 53 | ### 3. Restart your AI client 54 | 55 | After restarting, the MCP Tools should be available in your AI client's interface. 56 | _On Windows, you may need to manually kill the process for the changes to take effect._ 57 | 58 | ## Documentation 59 | 60 | ### What's an MCP? 61 | 62 | Model Context Protocol (MCP) is a standardized way for AI models to interact with external tools and services. It allows AI assistants to perform actions like running database queries, accessing external APIs, or manipulating files through a defined protocol interface. 63 | 64 | MCPs extend AI capabilities beyond conversation by providing structured access to tools and data sources without requiring direct integration into the AI model itself. 65 | 66 | ### Tutorial: Setting up with Claude Desktop 67 | 68 | Claude Desktop is one of many AI clients that support MCP servers. Here's how to set it up on Windows: 69 | 70 | 1. Press `Windows Key + R` to open the Run dialog 71 | 2. Type `%appdata%\Claude` and press Enter 72 | 3. Create a new file called `claude_desktop_config.json` if it doesn't exist already 73 | 4. Add the configuration from step 2 of the setup instructions above 74 | 5. Save the file and restart Claude Desktop 75 | 6. You should now see the SQLite tools available in your Claude interface 76 | 77 | ## License 78 | 79 | This project is licensed under the MIT License. 80 | 81 | ## Contributing 82 | 83 | This project was created primarily for learning purposes. However, if you'd like to contribute, feel free to submit a Pull Request and I'll review it. 84 | 85 | Thanks for your interest! 86 | ``` -------------------------------------------------------------------------------- /utils/version.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const VERSION = "0.0.1"; 2 | ``` -------------------------------------------------------------------------------- /utils/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { z } from "zod"; 2 | 3 | export const ReadQuerySchema = z.object({ 4 | query: z.string().describe("SELECT SQL query to execute"), 5 | }); 6 | ``` -------------------------------------------------------------------------------- /utils/parseArguments.ts: -------------------------------------------------------------------------------- ```typescript 1 | export function getArgumentValue(flag: string): string | undefined { 2 | const index = process.argv.indexOf(flag); 3 | return index !== -1 && index + 1 < process.argv.length ? process.argv[index + 1] : undefined; 4 | } 5 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "./dist", 8 | "resolveJsonModule": true 9 | }, 10 | "include": [ 11 | "./**/*.ts" 12 | ], 13 | "exclude": ["node_modules"] 14 | } 15 | 16 | ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | FROM node:22.12 AS builder 2 | 3 | RUN apt-get update && apt-get install -y libsqlite3-dev 4 | 5 | # Sorry for installing pnpm. I've tried to do it w npm but couldn't 6 | RUN npm install -g pnpm 7 | 8 | WORKDIR /app 9 | 10 | COPY package.json pnpm-lock.yaml ./ 11 | 12 | ENV npm_config_build_from_source=true 13 | ENV npm_config_sqlite=/usr 14 | 15 | RUN pnpm install --ignore-scripts 16 | # sqlite3 package was being failing to be installed. I have to rebuilt it here 17 | RUN cd node_modules/sqlite3 && pnpm rebuild 18 | 19 | COPY . . 20 | RUN pnpm run build 21 | 22 | FROM node:22.12 AS release 23 | 24 | WORKDIR /app 25 | 26 | COPY --from=builder /app/dist /app/dist 27 | COPY --from=builder /app/node_modules /app/node_modules 28 | COPY --from=builder /app/package.json /app/package.json 29 | 30 | ENTRYPOINT ["node", "dist/index.js"] 31 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "sqlite_server", 3 | "version": "0.0.1", 4 | "description": "MCP Server for SQLite. Implemented with Typescript", 5 | "main": "index.ts", 6 | "bin": { 7 | "mcp-server-sqlite": "dist/index.js" 8 | }, 9 | "bugs": "https://github.com/javsanmar5/mcp-server.sqlite/issues", 10 | "files": [ 11 | "dist" 12 | ], 13 | "scripts": { 14 | "build": "tsc && shx chmod +x dist/*.js", 15 | "prepare": "npm run build", 16 | "watch": "tsc --watch", 17 | "test": "test" 18 | }, 19 | "author": "Javier Santos Martín", 20 | "license": "MIT", 21 | "packageManager": "pnpm@latest", 22 | "dependencies": { 23 | "@modelcontextprotocol/sdk": "^1.6.1", 24 | "@types/node": "^22.13.9", 25 | "sqlite": "^5.1.1", 26 | "sqlite3": "^5.1.7", 27 | "ts-node": "^10.9.2", 28 | "typescript": "^5.8.2", 29 | "zod": "^3.24.2", 30 | "zod-to-json-schema": "^3.24.3" 31 | }, 32 | "devDependencies": { 33 | "shx": "^0.3.4" 34 | } 35 | } 36 | ``` -------------------------------------------------------------------------------- /utils/SqliteDatabase.ts: -------------------------------------------------------------------------------- ```typescript 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import os from "os"; 4 | import sqlite3 from "sqlite3"; 5 | import { open, Database } from "sqlite"; 6 | 7 | export class SqliteDatabase { 8 | private dbPath: string; 9 | 10 | constructor(dbPath: string) { 11 | if (dbPath.startsWith("~")) { 12 | dbPath = path.join(os.homedir(), dbPath.slice(1)); 13 | } 14 | this.dbPath = dbPath; 15 | 16 | const dir = path.dirname(this.dbPath); 17 | fs.mkdirSync(dir, { recursive: true }); 18 | } 19 | 20 | async initDatabase(): Promise<Database> { 21 | const db = await open({ 22 | filename: this.dbPath, 23 | driver: sqlite3.Database, 24 | }) 25 | return db; 26 | } 27 | 28 | // Params is there for the future. 29 | async executeQuery(query: string, params?: any[]): Promise<any[]> { 30 | const db = await this.initDatabase(); 31 | 32 | try { 33 | const result = await db.all(query, params); 34 | return result; 35 | 36 | } catch (error) { 37 | console.error("Database error:", error); 38 | throw error; 39 | 40 | } finally { 41 | await db.close(); 42 | } 43 | } 44 | } 45 | ``` -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 2 | import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 4 | import zodToJsonSchema from "zod-to-json-schema"; 5 | import { z } from "zod"; 6 | 7 | import { VERSION } from "./utils/version.js"; 8 | import * as types from './utils/types.js'; 9 | import { SqliteDatabase } from "./utils/SqliteDatabase.js"; 10 | import { getArgumentValue } from "./utils/parseArguments.js"; 11 | 12 | const dbPath: string | undefined = getArgumentValue("--db-path"); 13 | if (!dbPath) throw new Error("A dbpath must be specified by the argument: --db-path"); 14 | 15 | const db: SqliteDatabase = new SqliteDatabase(dbPath); 16 | 17 | const server = new Server( 18 | { 19 | name: "sqlite-mcp-server", 20 | version: VERSION, 21 | }, 22 | { 23 | capabilities: { 24 | tools: {}, 25 | } 26 | } 27 | ); 28 | 29 | server.setRequestHandler(ListToolsRequestSchema, async () => { 30 | return { 31 | tools: [ 32 | { 33 | name: "read_query", 34 | description: "Execute a SELECT query on a table of the SQLite database", 35 | inputSchema: zodToJsonSchema(types.ReadQuerySchema) 36 | }, 37 | { 38 | name: "list_tables", 39 | description: "List all tables on the SQLite database", 40 | inputSchema: zodToJsonSchema(types.ReadQuerySchema) 41 | } 42 | ] 43 | } 44 | }); 45 | 46 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 47 | const { name, arguments: args } = request.params; 48 | 49 | try { 50 | if (!args) 51 | throw new Error("Arguments are required"); 52 | 53 | switch (name) { // switch-case because there will be multiple options 54 | case "read_query": { 55 | const { query, params } = args as { query: string; params?: any[] }; 56 | 57 | if (typeof query !== "string") 58 | throw new Error("Invalid arguments: expected 'query' to be a string"); 59 | if (!query.trim().toUpperCase().startsWith("SELECT")) 60 | throw new Error("Only SELECT queries are allowed for read_query"); 61 | 62 | const results = await db.executeQuery(query, params); 63 | return { 64 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 65 | }; 66 | } 67 | case "list_tables": { 68 | const query = "SELECT name FROM sqlite_master WHERE type='table';" 69 | const results = await db.executeQuery(query); 70 | return { 71 | content: [{ type: "text", text: JSON.stringify(results, null, 2) }], 72 | }; 73 | 74 | } 75 | default: 76 | throw new Error(`Unknown tool: ${request.params.name}`); 77 | } 78 | 79 | } catch (error) { 80 | if (error instanceof z.ZodError) { 81 | throw new Error(`Invalid input: ${JSON.stringify(error.errors)}`); 82 | } 83 | throw error; 84 | } 85 | }); 86 | 87 | async function runServer() { 88 | const transport = new StdioServerTransport(); 89 | await server.connect(transport); 90 | } 91 | 92 | runServer().catch((error) => { 93 | console.error("Fatal error in main():", error); 94 | process.exit(1); 95 | }); 96 | ```