jmd-mcp-sql
Enables natural language database interaction with SQLite through JMD documents, allowing CRUD operations and schema management via simple text format.
README
jmd-mcp-sql
MCP server that exposes a SQLite database through four JMD tools — a natural language database interface for LLM-driven workflows.
What is JMD?
JMD (JSON Markdown) is a lightweight
document format that combines Markdown headings with key: value pairs. It is
designed as a structured data format that LLMs can read and write naturally —
without JSON brackets or SQL syntax. A heading line sets the document type and
target table; the body carries the data:
# Order
id: 42
status: shipped
total: 149.99
A prefix on the heading selects the operation: # for data, #? for queries,
#! for schema, #- for deletes. See the
JMD specification for the full format
definition.
Tools
| Tool | # Data |
#? Query |
#! Schema |
#- Delete |
|---|---|---|---|---|
open |
Open database / show status | — | — | — |
read |
SELECT by fields | SELECT with filters + aggregation | PRAGMA (describe table) | — |
write |
INSERT OR REPLACE | — | CREATE / ALTER TABLE | — |
delete |
— | — | DROP TABLE | DELETE WHERE |
All inputs and outputs are JMD documents. The LLM speaks JMD — no SQL required.
Installation
Install from PyPI:
pip install jmd-mcp-sql
Or with uv (no manual install needed — uvx fetches it on demand):
uvx jmd-mcp-sql
Alternatively, install directly from GitHub:
pip install git+https://github.com/ostermeyer/jmd-mcp-sql.git
Configuration
The server runs as a stdio-based MCP server. Without arguments it starts with the bundled Northwind demo database. Pass a path to use your own SQLite file:
jmd-mcp-sql /path/to/your.db
The demo database ships as northwind.sql (plain text, version-controlled). On the
first run without an explicit path, the server creates northwind.db from that dump
automatically.
Claude Code
Add the server via CLI:
claude mcp add --transport stdio sql -- uvx jmd-mcp-sql
With a custom database:
claude mcp add --transport stdio sql -- uvx jmd-mcp-sql /path/to/your.db
This writes a .mcp.json in the project root (shareable via version control).
You can also create it manually:
{
"mcpServers": {
"sql": {
"command": "uvx",
"args": ["jmd-mcp-sql"]
}
}
}
Claude Desktop / Cowork
Claude Cowork runs inside Claude Desktop. MCP servers configured in the Desktop config are automatically available in Cowork sessions.
Edit claude_desktop_config.json:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"sql": {
"command": "uvx",
"args": ["jmd-mcp-sql"]
}
}
}
With a custom database:
{
"mcpServers": {
"sql": {
"command": "uvx",
"args": ["jmd-mcp-sql", "/path/to/your.db"]
}
}
}
Restart Claude Desktop after saving the file. The server will appear as a tool in both Chat and Cowork mode.
VS Code
Create .vscode/mcp.json in the project root:
{
"servers": {
"sql": {
"type": "stdio",
"command": "uvx",
"args": ["jmd-mcp-sql"]
}
}
}
Alternatively, add it to your VS Code settings.json (user or workspace):
{
"mcp": {
"servers": {
"sql": {
"type": "stdio",
"command": "uvx",
"args": ["jmd-mcp-sql"]
}
}
}
}
JMD Document Syntax
Every document starts with a heading line that sets the document type and table name,
followed by key: value pairs (one per line):
# Product → data document (exact lookup / insert-or-replace)
#? Product → query document (filter / list / aggregate)
#! Product → schema document (describe / create / drop table)
#- Product → delete document (delete matching records)
key: value → string, integer, or float — inferred automatically
key: true/false → boolean
Opening a Database
Open a different SQLite database at any time:
open("# Database\npath: /path/to/mydb.db")
If the file does not exist, a new empty database is created. The previous database is closed automatically. The response uses frontmatter for metadata and lists tables in the body:
path: /path/to/mydb.db
table-count: 3
# Database
## tables[]
- Customers
- Orders
- Products
Check which database is currently active:
open("# Database")
Path Restriction
The server reads optional settings from ~/.config/jmd/sql.jmd:
# Config
root: /Users/me/data
| Field | Default | Description |
|---|---|---|
root |
(none) | Restricts open to databases under this directory tree |
Without a config file, no restrictions apply and the server accepts any path.
Discovering the Database
To see which tables exist, read each table's schema:
read("#! Customers")
This returns a #! document with column names, JMD types, and modifiers
(readonly = primary key, optional = nullable).
Typical Workflows
List all rows (small tables only):
read("#? Orders")
Filter rows — equality:
read("#? Orders\nstatus: shipped")
Filter rows — comparison:
read("#? Orders\nFreight: > 50")
Filter rows — alternation (OR):
read("#? Orders\nShipCountry: Germany|France|UK")
Filter rows — contains (case-insensitive substring):
read("#? Customers\nCompanyName: ~Corp")
Filter rows — regex pattern:
read("#? Products\nProductName: ^Chai.*")
Filter rows — negation (composes with any operator):
read("#? Orders\nShipCountry: !Germany")
read("#? Products\nProductName: !^LEGACY.*")
Look up one record:
read("# Customers\nid: 42")
Insert or replace a record:
write("# Orders\nid: 1\nstatus: pending\ntotal: 99.90")
Create a table:
write("#! Products\nid: integer readonly\nname: string\nprice: float optional")
Delete a record:
delete("#- Orders\nid: 1")
Drop a table:
delete("#! OldTable")
Pagination
Always use pagination when querying tables that may contain many rows.
Use frontmatter fields before the #? heading to control pagination:
read("page-size: 50\npage: 1\n\n#? Orders")
The response carries pagination metadata as frontmatter — before the root heading:
total: 830
page: 1
pages: 17
page-size: 50
# Orders
## data[]
- OrderID: 10248
...
Count only (no rows returned):
read("count: true\n\n#? Orders")
Returns:
count: 830
# Orders
Use total and pages to determine whether to fetch more pages.
For tables with fewer than ~20 rows pagination is optional.
Field Projection
Use select: frontmatter to return only specific columns. This keeps
responses small and context windows focused.
read("select: OrderID, EmployeeID\npage-size: 50\n\n#? Orders")
Works with both # (data) and #? (query) documents. When combined with
aggregation, select: filters the result columns after the GROUP BY.
Joins
Use join: frontmatter to query across multiple tables in one call.
The value is <TableName> on <JoinColumn> (INNER JOIN, equi-join on a
column that exists in both tables).
read("join: Order Details on OrderID\nsum: UnitPrice * Quantity * (1 - Discount) as revenue\ngroup: EmployeeID\nsort: revenue desc\n\n#? Orders")
Multiple joins — comma-separated in a single join: value:
join: Order Details on OrderID, Employees on EmployeeID
Expression syntax — use <expression> as <alias> in aggregate functions
to compute derived values across joined columns:
sum: UnitPrice * Quantity * (1 - Discount) as revenue
The alias becomes the result column name. Without as, the default alias
<func>_<field> applies (e.g. sum_Freight).
Allowed in expressions: column names, numeric literals, arithmetic operators
(+, -, *, /), and standard SQL functions (SUM, AVG, ROUND, …).
Subqueries and SQL keywords are not permitted.
Projection rules for join queries:
- Unambiguous columns (appear in exactly one table) resolve automatically.
- Join key columns always resolve to the main table.
- Columns present in multiple tables (other than join keys) require explicit
qualification — specify them via
select:or filter on the unambiguous side.
Aggregation
Aggregation is expressed as frontmatter before the #? heading.
QBE filter fields narrow rows before aggregation (SQL WHERE).
The having: key filters after aggregation (SQL HAVING).
| Key | SQL | Result column name |
|---|---|---|
group: f1, f2 |
GROUP BY | grouping keys pass through unchanged |
sum: field |
SUM(field) | sum_field |
avg: field |
AVG(field) | avg_field |
min: field |
MIN(field) | min_field |
max: field |
MAX(field) | max_field |
count |
COUNT(*) | count |
Multiple fields per function: sum: Freight, Total → sum_Freight and sum_Total.
| Frontmatter | Meaning |
|---|---|
sort: sum_revenue desc, EmployeeID asc |
ORDER BY (multiple columns, mixed) |
having: count > 5 |
HAVING COUNT(*) > 5 |
having: sum_Freight > 1000, count > 2 |
HAVING … AND … (comma = AND) |
having: supports: >, >=, <, <=, =.
sort: references any result column — grouping keys or aggregate aliases.
page-size: and page: apply to the aggregated result set.
Example — top 3 employees by revenue:
read("group: EmployeeID\nsum: revenue\nsort: sum_revenue desc\npage-size: 3\n\n#? OrderDetails")
Error Handling
All tools return a # Error document on failure:
# Error
status: 400
code: not_found
message: No records found in Orders
Check the code field to decide how to proceed.
Specification
The JMD format is documented at jmd-spec.
License
Copyright © 2026 Andreas Ostermeyer.
Licensed under the Apache License, Version 2.0 — see LICENSE. The JMD ecosystem is open: the specification is CC BY 4.0, the Python and JavaScript reference implementations are Apache 2.0, and this server matches. No copyleft, no dual licensing, no CLA. Use it, fork it, extend it, ship it — attribution preserved per Apache 2.0 § 4.
License history
This project has changed license twice during its early development:
- 0.4 – 0.7.1 (MIT, yanked from PyPI as the default install target)
- 0.8.0 – 0.9.x (AGPL-3.0, reflecting a brief period in which a commercial deployment path was under consideration)
- 0.10.0 and later (Apache 2.0, aligned with the rest of the JMD ecosystem)
Users who installed any of the prior versions retain the rights those licenses granted for those specific artifacts. License changes are not retroactive. See CHANGELOG.md for the rationale.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。