
Cds Db Nlquery Mcp
io.github.shahidla由 GitHub 账号 shahidla 发布v0.7.3更新于 Oct 27, 2026
MCP server for natural-language queries against SAP CAP db-layer entities via real CQN/SQL JOINs.
概览
本地 MCP 服务器,让助手用自然语言查询 SAP CAP 的 CDS db 层实体,并通过真实的 CQN/SQL JOIN 返回数据行。
- 可以做什么
- 用自然语言提出关于 CDS db 层模式的问题,服务器把模式和问题发给所配置的 LLM,由其转换为查询描述符。
- 执行只读 SELECT 查询,支持过滤条件、两列之间的比较以及不区分大小写的文本匹配。
- 沿 CDS 关联路径生成真实 SQL JOIN,由数据库执行跨实体连接。
- 把结果行返回给客户端,并在构建查询前应用实体白名单和列黑名单。
- 适用场景
- 适用于已有配置好数据库的 SAP CAP 项目,希望助手在不写 SQL 的情况下探索或回答 db 层数据问题。它针对 db/ 层而非 OData 服务层,且为只读。
- 运行要求
- 以 npm 包形式通过 stdio 在本机运行,需安装在含 @sap/cds >= 7 且已配置数据库(HANA Cloud、SQLite、PostgreSQL)的 CAP 项目中。需要 MCP 客户端、以项目根目录作为工作目录,以及一个 LLM 提供方密钥:ANTHROPIC_API_KEY 或 OPENAI_API_KEY(可选配模型与 base URL)。可选:MCP_ALLOWED_ENTITIES、MCP_BLOCKED_COLUMNS、MCP_MAX_ROWS、MCP_MODEL_PATH、MCP_DB_USER、MCP_DB_PASSWORD。
安装
在 SourceWeft 中
- 打开 控制台中的 Cds Db Nlquery Mcp,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
@shahid.la/cds-db-nlquery-mcp
MCP server for natural language queries against CDS db-layer entities (db/schema.cds).
Ask questions about your database in plain English. The server discovers your schema automatically, generates real SQL JOINs using CDS, and returns rows. No hardcoded queries, no SQL, no schema configuration. Bring your own LLM: Anthropic or OpenAI, or any OpenAI-compatible endpoint (Azure OpenAI, Groq, Ollama, local models, etc.).
Targets the
db/layer, not OData services. If your entities are exposed as OData services, use an MCP package that targets the service layer instead.
Read-only. This package only executes SELECT queries. No INSERT, UPDATE, or DELETE.
Prerequisites
- A CAP project with
@sap/cds >= 7 - A configured CDS database (HANA Cloud, SQLite, PostgreSQL)
- An MCP client (Claude Code, Claude Desktop, or any MCP-compatible host)
- An API key for an LLM provider (see LLM provider below), used to translate your question into a query, not to answer it
Quick start
1. Install in your CAP project
2. Create .mcp.json in your CAP project root (same folder as package.json)
Set cwd to the absolute path of your CAP project root, where db/schema.cds lives. See LLM provider for using OpenAI, Gemini, or another provider instead.
npx -y re-resolves the package on every server start (from npm's cache once
downloaded, not a fresh network fetch each time, but still an extra resolution
step). If you've run npm install @shahid.la/cds-db-nlquery-mcp already, you
can point "command" at the installed binary directly instead,
node_modules/.bin/cds-db-nlquery-mcp, for a slightly more predictable startup,
especially in production.
3. Open your project in Claude Code and ask a question
Example response: the server tells the client to render rows as a vertical field/value list, not a markdown table (real output, verified against a live deployment):
How it works
When you ask a question:
- Your MCP client (e.g. Claude Code) calls the
natural_language_querytool with your question. - The MCP server has already loaded your CDS schema at startup: entity names, columns, and associations. It sends this schema plus your question to the LLM provider you configured (Anthropic, OpenAI, or any OpenAI-compatible endpoint), which translates it into a structured query descriptor.
- The server executes a single CDS query. CDS association paths (
customer.BU_SORT1) generate real SQL JOINs, executed by your database, not by JavaScript. Scales to production data volumes. - Results come back to your MCP client, which formats and presents the answer.
The LLM call in step 2 is a small, cheap planning step (translating your question into JSON). It does not need a large or expensive model. A fast/cheap tier model is recommended.
Configuration
All configuration is via environment variables in the .mcp.json env block.
LLM provider
A small LLM call translates your question into a query descriptor. Bring your own provider, set ONE of the following in the env block:
Anthropic (Claude):
OpenAI, or any OpenAI-compatible provider:
OPENAI_MODEL accepts any model name. Use whatever your provider expects. Set OPENAI_BASE_URL to point at a different OpenAI-compatible endpoint:
This is a planning-only call (question to JSON descriptor). A fast/cheap tier model is sufficient and recommended.
Security
Development
The server uses your project's existing database connection, whatever CDS has configured in default-env.json or your service binding. No extra setup needed.
Production
Important: this package queries the database directly via cds.run(). It does not go through the CAP service layer, so CAP @requires and @restrict annotations are not enforced. Access control is entirely your responsibility at the database and configuration level.
Step 1: Create a dedicated read-only database user
This user can only read. No write access. HANA itself enforces it, independently of this package.
Step 2: Point the server at it with MCP_DB_USER/MCP_DB_PASSWORD
The server connects with these credentials instead of inheriting your app's own database connection. Host, port, and schema are reused automatically, only the user/password are overridden:
If you keep these in a separate gitignored file instead of directly in .mcp.json,
that's fine too. The server just reads process.env, same as any other variable here.
Step 3: Set MCP_ALLOWED_ENTITIES
This is enforced on the entity you query directly and on any entity reached via
an association join in select/where. For example, if Customers isn't in the allowlist,
a query against Orders can't read Customers data through a customer.NAME join
path either. The database user restricts access at the HANA level;
MCP_ALLOWED_ENTITIES adds a second layer at the application level. Use both.
Joins
The server reads CDS associations from your schema. When the LLM references customer.BU_SORT1 in a query, CDS generates a real SQL JOIN, executed by the database.
Multiple associations in one query work:
"Show loans in the MINING sector with borrower name and current DTI"
Generates a single SQL statement joining Loans → BusinessPartners → BCA_DTI in one database round-trip.
One constraint: do not select the same column name from two different entities in the same query (e.g. LOAN_ID from both the main entity and a joined entity). The database rejects duplicate column names. Claude is instructed to avoid this, but worth being aware of.
Comparing two columns to each other
Most filters compare a column to a fixed value (DTI_RATIO > 5). For questions like "which loans have collateral worth less than the loan amount", the comparison is between two columns instead. Use valCol in place of val:
Either side can be an association path. This is handled by the same join mechanism as everything else. No separate query, no JavaScript-side comparison.
Filtering by a coded value's human meaning
If a column has a @Common.Text value-help association (see Coded values), filter via the text field directly rather than guessing the underlying code:
like is case-insensitive (enforced via UPPER() on both sides, not relying on database collation), so the exact case of the value doesn't need to match.
Column and entity labels
The LLM only sees what's in your CDS model: column names, types, and any labels you've annotated. Plain code comments (// customer name) are invisible to it. Without a label, an ambiguous column name can cause the LLM to guess wrong, regardless of how capable the model is.
@title: reused automatically, zero extra work
If your schema already has SAP's standard @title annotation (common in projects with a Fiori UI or OData service), the server picks it up automatically. You don't need to do anything:
@NLP.label: for disambiguation @title isn't meant for
Use this when you need to tell the LLM something a UI-facing label shouldn't say, e.g. "don't use this column for X." This was a real bug we hit: a BU_TYPE code column (1=person, 2=organisation) was being picked by the LLM whenever a question asked for a customer's "name," because nothing told it otherwise. The fix:
After adding these two labels, even the cheapest tier model (claude-haiku-4-5) picked the right column every time. Fix ambiguity at the schema level, not by tweaking prompts per-bug. It's permanent and works regardless of which LLM provider or model you use.
Precedence: @NLP.label is checked first, falls back to @title, falls back to the raw column/entity name if neither is set.
Other @NLP annotations
All optional. The package works without any of them. But for any column whose name alone could be misread (codes, abbreviations, anything that looks like one thing but means another), a label is the difference between the LLM guessing and the LLM knowing.
Coded values: what does STATUS = 'C' mean?
Labels solve "what does this column mean." A separate problem: what does a coded
value in that column mean? STATUS = 'C'. Closed? Cancelled? Confirmed?
There are two ways to tell the LLM, and which one to use depends on whether the value list can change without a code deploy.
@Common.Text: for business-configurable codes (the common case)
Most status/type codes are business data. Someone in operations might introduce a
new value next quarter, and that should never require touching the schema file. The
SAP-standard mechanism for this is @Common.Text, pointing through an association to
a small lookup/check table, the same pattern Fiori elements uses for value-help
dropdowns:
Seed it with {CODE: 'A', TEXT: 'Active'}, {CODE: 'C', TEXT: 'Closed'}. Adding a
new status later is an INSERT into LoanStatusCodes, no schema change, no
redeploy. The server detects @Common.Text, tells the LLM a readable value is
available via the association path, and the LLM includes it in select using the
normal join mechanism. No extra code on our side, it's the same association-path
JOIN used everywhere else in this package.
CDS enum: only for sets that are genuinely fixed forever
This is compile-time: adding a value means editing schema.cds and redeploying.
Appropriate only for values that are tied to actual program logic anyway (so a code
change would be required regardless), not for business classifications. We initially
used enum for our own demo's status fields and walked it back to @Common.Text for
exactly this reason. It's documented here as a contrast, not a recommendation.
Rule of thumb: reach for @Common.Text by default. Use enum only when you're sure
the list can never grow without code changing anyway.
When a column has @Common.Text, query results get the raw code back as normal.
Presentation (showing "Closed" instead of "C") is up to whichever LLM renders the
final answer, using the readable value it fetched via the join.
Startup log
When the server starts, check the output panel in Claude Code:
0 entities at startup? Check that cwd in .mcp.json points to your CAP project root and that MCP_MODEL_PATH matches your schema folder name.
WARNING: No LLM provider configured? Set ANTHROPIC_API_KEY or OPENAI_API_KEY in the .mcp.json env block, see LLM provider.
WARNING: MCP_DB_USER not set? Expected during development, the server is using the same database connection as your CAP app. Before production use, see Security → Production for creating a dedicated read-only user.
Testing this against your own deployment
npm install gives you src/, the MCP server, and nothing else. That's the
entire published package. The GitHub repository also has an
examples/capability-demo/ folder with a real CDS schema, seed data, ~35
example queries with verified expected results, and four scripts to actually
exercise them against your own database (not just read about it): one runs the
example queries directly against the internal functions, one lets you ask your
own question end-to-end through a real LLM, one runs every example question's
natural-language text through a real LLM and checks the result, and one spawns
src/mcp-server.js itself as a real child process and drives it through the
actual MCP stdio protocol, the same way Claude Code or Claude Desktop would,
rather than calling internal functions directly. None of this ships with
npm install. Clone the repo if you want it. See that folder's own README for details.
How releases are tested
npm test (126 unit tests) runs against a mocked database. Fast, but it can't
catch backend-specific behavior (a real HANA deployment has rejected things the
mocks happily accepted, more than once). Before tagging a release, npm run test:deployment is run against a real CAP project connected to live HANA
Cloud. It requires real credentials and a deployed examples/capability-demo/
schema, so it isn't part of npm test or CI, but it is a required manual step,
not an optional one. examples/capability-demo/smoke-test-server.js is also
run. It spawns src/mcp-server.js itself and drives it over the real MCP
stdio protocol with a real LLM call, not just the internal functions directly.
RELEASE_VERIFICATION.md is the actual, append-only record of this: real output from a real run against live BTP HANA, captured and committed before each release, not just claimed.
License
MIT
来源:README.md,提交 64201d3
工具
0版本历史
1- v0.7.3最新Sep 16, 2026
评分
已满足适用检查项的 50%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通25 分
请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。
- 已声明认证方式10 分
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分