#
tokens: 5263/50000 12/12 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | 
```