oracle-mcp
Enables AI agents to safely inspect Oracle Database schemas, objects, and PL/SQL source with a strict read-only guard, without ever modifying data.
README
oracle-mcp
A read-only Oracle Database server for the Model Context Protocol. It lets AI agents (Claude Desktop, Claude Code, Cursor, VS Code agents, OpenAI Agents, …) safely inspect large legacy Oracle schemas — thousands of tables, hundreds of packages, views, synonyms, triggers, sequences and PL/SQL source — without ever modifying data.
It is designed as a standalone module that runs alongside an existing "Engineering MCP" (GitLab / Redmine / Taiga / ERPNext): one agent, several MCP servers.
Safety model in one line: the server only ever issues
SELECTand data-dictionary reads, every object name is passed as a bind variable, free-form SQL is checked by a fail-closed read-only guard, and the database account itself should be granted read-only. Defence in depth, not a single gate.
Table of contents
- Features
- Requirements
- Installation
- Configuration
- Wiring it into an agent
- Architecture
- Tool reference
- Security considerations
- Examples
- Testing
- Troubleshooting
Features
- 24 focused tools covering search, describe, DDL, source, dependencies, indexes, constraints,
triggers, synonyms, statistics, invalid objects and guarded
SELECTexecution. - Read-only by construction — a SQL guard that rejects everything but a single, comment-free
SELECT/WITH … SELECT. - Bind-variable everywhere — object names and keywords are never concatenated into SQL.
- Bounded & safe — hard row cap (default 1000), per-statement timeout, ResultSet cleanup.
- Connection pooling with transparent reconnect (thick mode / Oracle Instant Client).
- Structured logging to stderr (timestamp, tool, elapsed, rows, schema, SQL) — never secrets.
- Typed error taxonomy — connection / validation / invalid-SQL / permission / not-found / timeout / oracle.
- Strongly typed (TypeScript strict) and tested (48 unit tests for the guard & helpers).
Requirements
- Node.js ≥ 18
- Oracle Instant Client installed and on the library path (this build uses oracledb thick mode).
- Windows: the Instant Client folder on
PATH. - Linux/macOS: on
LD_LIBRARY_PATH/DYLD_LIBRARY_PATH, or setORACLE_CLIENT_LIB_DIR.
- Windows: the Instant Client folder on
- Network access to the database and a read-only Oracle account (see Security).
Installation
git clone <your-repo>/oracle-mcp.git
cd oracle-mcp
npm install
npm run build # compiles src/ → dist/
Verify without a database:
npm test # 48 unit tests (SQL guard, identifiers, formatting)
Smoke-test against a real database (read-only):
ORACLE_USER=... ORACLE_PASSWORD=... ORACLE_CONNECT_STRING=host:port/service \
npx tsx scripts/integration-check.ts
Configuration
Configuration is via environment variables. The server loads a .env file from its own package
directory automatically (copy .env.example → .env), so secrets live next to the server and out
of your agent config. Config is validated at startup; the server fails fast with a readable,
secret-free message if anything is missing.
Databases (one or many)
The server can inspect several Oracle databases at once. Every tool takes an optional database
argument; when omitted it uses the default.
Single database:
ORACLE_USER="readonly_user"
ORACLE_PASSWORD="change_me"
ORACLE_CONNECT_STRING="host:port/service"
Multiple databases — list the names, then supply per-name vars with the prefix ORACLE_<NAME>_
(name upper-cased, non-alphanumerics → _):
ORACLE_DATABASES=tcil,sbi_eforex,ybl
ORACLE_DEFAULT_DATABASE=tcil
ORACLE_TCIL_USER="…" ORACLE_TCIL_PASSWORD="…" ORACLE_TCIL_CONNECT_STRING="host:port/service"
ORACLE_SBI_EFOREX_USER="…" ORACLE_SBI_EFOREX_PASSWORD="…" ORACLE_SBI_EFOREX_CONNECT_STRING="host:port/service"
ORACLE_YBL_USER="…" ORACLE_YBL_PASSWORD="…" ORACLE_YBL_CONNECT_STRING="host:port/service"
Pools are created lazily per database — configuring ten costs nothing until they're queried.
Wrap passwords in double quotes so $/# are taken literally.
Connect string tip: for a PDB use the service name form
host:port/service. The olderhost:port:SIDform is not Easy Connect — convert it (…:port/service) or use a tnsnames alias.
Shared settings
| Variable | Default | Description |
|---|---|---|
ORACLE_CLIENT_LIB_DIR |
(from PATH) | Instant Client dir. If unset, discovered via PATH/LD_LIBRARY_PATH. |
ORACLE_TNS_ADMIN |
— | Dir containing tnsnames.ora/sqlnet.ora, if used. |
ORACLE_MAX_ROWS |
1000 |
Hard cap on rows any tool returns (also the max a caller may request). |
ORACLE_QUERY_TIMEOUT_MS |
15000 |
Per-statement timeout (thick-mode callTimeout). |
ORACLE_POOL_MIN / _MAX / _INCREMENT |
1 / 4 / 1 |
Connection pool sizing (per database). |
ORACLE_POOL_TIMEOUT |
60 |
Idle-connection trim (seconds). |
ORACLE_DEFAULT_SCHEMA |
— | Default owner for owner-scoped tools when schema is omitted. |
LOG_LEVEL |
info |
error | warn | info | debug (logs → stderr). |
Wiring it into an agent
oracle-mcp speaks MCP over stdio. Add it next to your Engineering MCP.
Claude Desktop / Claude Code (claude_desktop_config.json / .mcp.json) — no secrets here; the
server reads its own .env:
{
"mcpServers": {
"engineering": { "command": "node", "args": ["/path/to/mcp-erpnext/src/index.js"] },
"oracle": {
"command": "node",
"args": ["/path/to/oracle-mcp/dist/index.js"],
"cwd": "/path/to/oracle-mcp"
}
}
}
Credentials live in oracle-mcp/.env (gitignored), not in the agent config. Keeping Oracle in its
own server (rather than merging into the JS Engineering MCP) isolates the security-critical
database surface and lets you grant/deploy it independently.
Architecture
┌──────────────────────────────────────────────┐
AI agent ──stdio──▶ │ index.ts (McpServer, StdioServerTransport) │
(Claude/Cursor/…) └───────────────┬──────────────────────────────┘
│ registers 24 tools
┌───────────────▼───────────────┐
│ tools/oracle/* │ runSelect · executionPlan · ddl
│ (thin handlers, zod schemas) │ · 20 declarative metadata tools
└───────┬───────────────┬────────┘
guarded SQL │ │ built SQL + binds
┌───────────▼──────┐ ┌─────▼─────────────────────┐
│ validation/ │ │ oracle/client.ts │
│ sqlGuard.ts │ │ • timeout (callTimeout) │
│ (fail-closed) │ │ • row cap + truncation │
└──────────────────┘ │ • ResultSet cleanup │
│ • error → taxonomy │
└─────┬─────────────────────┘
│ pooled connection
┌─────▼───────────────┐
│ oracle/pool.ts │ thick init · pool · reconnect
└─────┬───────────────┘
▼
Oracle DB (ALL_* dictionary + DBMS_METADATA/DBMS_XPLAN)
cross-cutting: config/env.ts (zod-validated) logging/logger.ts (stderr, redacted)
errors.ts (typed taxonomy) utils/ (identifiers, formatting)
Folder structure
oracle-mcp/
├── src/
│ ├── index.ts # server bootstrap + graceful shutdown
│ ├── config/env.ts # env loading & validation (zod)
│ ├── logging/logger.ts # structured stderr logger (+ SQL redaction)
│ ├── errors.ts # OracleMcpError + Oracle→taxonomy mapping
│ ├── types/index.ts # shared types
│ ├── validation/sqlGuard.ts # read-only SQL guard ◀── security core
│ ├── utils/
│ │ ├── identifiers.ts # name validation, LIKE-pattern escaping
│ │ └── format.ts # Markdown tables / code blocks
│ ├── oracle/
│ │ ├── pool.ts # thick init, pool lifecycle, reconnect
│ │ └── client.ts # the single query choke-point
│ └── tools/oracle/
│ ├── context.ts # tool type + registration wrapper
│ ├── runSelect.ts # oracle_run_select (guarded)
│ ├── executionPlan.ts # oracle_show_execution_plan
│ ├── ddl.ts # oracle_get_object_ddl / oracle_get_view
│ ├── metadataTools.ts # 20 declarative dictionary tools
│ └── index.ts # catalogue + registerOracleTools()
├── tests/ # vitest unit tests
├── scripts/integration-check.ts
└── .env.example
Why these choices
- Standalone TS package, not merged into the JS Engineering MCP — isolates a security-sensitive surface, allows a strict-typed build and independent deployment/grants.
- Thick mode — chosen for this deployment (Instant Client present); enables the widest driver feature set. Thin mode would remove the client dependency if ever desired.
- Declarative metadata tools — the 20 dictionary tools share one safe shape (fixed SQL + binds + format), so adding a tool is a few lines and the security properties are uniform.
- One
OracleClientchoke-point — every query flows through it, so timeout, row cap, cleanup, error mapping and logging are enforced in exactly one place.
Tool reference
All tools are prefixed oracle_. Owner-scoped tools accept an optional schema; search tools accept
an optional limit (clamped to ORACLE_MAX_ROWS). Names may be given as OBJECT or SCHEMA.OBJECT.
| Tool | Key params | Purpose |
|---|---|---|
oracle_run_select |
sql, maxRows? |
Execute a guarded read-only SELECT. |
oracle_show_execution_plan |
sql |
EXPLAIN PLAN + DBMS_XPLAN for a SELECT (no data touched). |
oracle_list_schemas |
— | List owners/schemas visible to the account. |
oracle_list_tables |
schema?, keyword?, limit? |
List tables (optionally filtered). |
oracle_search_tables |
keyword |
Tables whose name contains a keyword. |
oracle_find_table |
table_name |
Locate a table across schemas, including synonyms. |
oracle_describe_table |
table_name, schema? |
Columns + types + nullability + comments. |
oracle_search_columns |
column_name |
Columns whose name contains a keyword (e.g. RISK). |
oracle_find_column |
column_name |
Tables having a column (exact matches first). |
oracle_get_indexes |
table_name |
Indexes with columns, uniqueness, type, status. |
oracle_get_constraints |
table_name |
PK/FK/UK/CHECK with columns, ref table, delete rule. |
oracle_find_triggers |
table_name |
Triggers on a table (timing, event, status). |
oracle_get_object_ddl |
object_name, object_type? |
Full CREATE DDL via DBMS_METADATA. |
oracle_get_view |
view_name |
View DDL + column list. |
oracle_get_package_source |
package_name |
Package specification source. |
oracle_get_package_body |
package_name |
Package body source. |
oracle_search_package |
package_name |
Find packages by name keyword. |
oracle_search_procedure |
procedure_name |
Find procedures/functions (standalone & packaged). |
oracle_search_source |
keyword, object_type? |
Full-text search of all PL/SQL source — references & callers. |
oracle_find_dependencies |
object_name, direction? |
used_by (callers) or uses (referenced). |
oracle_list_synonyms |
schema?, keyword?, target_table? |
Synonyms; target_table → "points to". |
oracle_get_table_statistics |
table_name |
Row count, blocks, avg row len, last analyzed. |
oracle_list_invalid_objects |
schema? |
Objects in INVALID state. |
oracle_describe_object |
object_name |
What an object is (type/owner/status) from ALL_OBJECTS. |
How common questions map to tools
| Question | Tool |
|---|---|
Where is MFX_GET_MARGIN defined? |
oracle_search_procedure → oracle_describe_object |
| Show package body | oracle_get_package_body |
Find all procedures calling MFX_GET_MARGIN |
oracle_find_dependencies (used_by) or oracle_search_source |
Every reference to mfx_transaction |
oracle_search_source |
Describe mfx_entity_master |
oracle_describe_table |
| Columns containing "risk" | oracle_search_columns |
| Indexes / FKs / triggers on a table | oracle_get_indexes / oracle_get_constraints / oracle_find_triggers |
| Explain this query | oracle_show_execution_plan |
| Synonyms pointing to a table | oracle_list_synonyms (target_table) |
| Invalid objects | oracle_list_invalid_objects |
Security considerations
Layers (defence in depth):
- Read-only account (primary wall). Grant the connection user only
CREATE SESSION+SELECTon the objects (or roles) it must inspect, plusSELECT_CATALOG_ROLEfor the dictionary. The MCP should be incapable of writing regardless of any bug above it. - SQL guard (
validation/sqlGuard.ts) for the one free-form tool (oracle_run_select) — it fails closed and rejects:- anything that is not a lone
SELECT/WITH … SELECT; INSERT/UPDATE/DELETE/MERGE/…, all DDL,GRANT/REVOKE,COMMIT/ROLLBACK;- PL/SQL blocks (
BEGIN/DECLARE),CALL,EXECUTE [IMMEDIATE],SELECT … INTO,FOR UPDATE; - dangerous packages (
DBMS_SQL,DBMS_SCHEDULER,DBMS_JOB,UTL_FILE,UTL_HTTP, …); - semicolons / multiple statements, and all comments/hints (a classic bypass vector);
- it analyses a code-only projection with string-literal contents blanked, so keywords or semicolons hidden inside literals can neither false-trigger nor smuggle a second statement.
- anything that is not a lone
- Bind variables for every object name / keyword in the 23 metadata tools — user input is a value, never SQL text. Identifiers are additionally validated against a strict character set.
- Bounds — hard row cap (
ORACLE_MAX_ROWS), per-statementcallTimeout, ResultSet cleanup. - No secret leakage — passwords are never logged; logs go to stderr only (stdout is the MCP channel); SQL is length-capped in logs.
Notes
oracle_show_execution_planrunsEXPLAIN PLAN, which writes to the session-private global temporaryPLAN_TABLE. That is scratch metadata, auto-discarded, and available even to read-only accounts — no production data is read or written.- The guard is intentionally strict; prefer a dedicated metadata tool over
oracle_run_selectwhen one exists. A rare false positive (e.g. a column literally named after a non-reserved keyword) can be worked around with an alias.
Examples
Agent: "Describe mfx_entity_master."
→ oracle_describe_table { table_name: "MFX_ENTITY_MASTER" }
Agent: "Find every procedure that references mfx_transaction."
→ oracle_search_source { keyword: "mfx_transaction", object_type: "PACKAGE BODY" }
Agent: "Show the body of MFX_GET_MARGIN."
→ oracle_get_package_body { package_name: "MFX_GET_MARGIN" }
Agent: "What foreign keys does mfx_transaction have?"
→ oracle_get_constraints { table_name: "MFX_TRANSACTION" }
Agent: "Explain: SELECT * FROM mfx_transaction WHERE trans_date > SYSDATE - 7"
→ oracle_show_execution_plan { sql: "SELECT * FROM mfx_transaction WHERE trans_date > SYSDATE - 7" }
Testing
npm test # unit: SQL guard (accept/reject matrix), identifiers, LIKE escaping
npm run typecheck # tsc --noEmit
npx tsx scripts/integration-check.ts # live smoke test (needs a DB; read-only)
The unit tests deliberately concentrate on the security guard — the accept set (SELECT/CTE,
literals containing forbidden words, escaped quotes, near-keyword identifiers) and the reject set
(DML/DDL, semicolons, comments/hints, PL/SQL, dangerous packages, q'…', oversize, non-string).
Troubleshooting
| Symptom | Cause / fix |
|---|---|
DPI-1047: Cannot locate a 64-bit Oracle Client library |
Instant Client not found. Install it and put it on PATH/LD_LIBRARY_PATH, or set ORACLE_CLIENT_LIB_DIR. |
ORA-12154 / ORA-12541 / ORA-12514 |
Bad connect string / no listener / unknown service. Use host:port/service (service name, not SID) or a valid tnsnames alias. |
ORA-01017: invalid username/password |
Wrong ORACLE_USER/ORACLE_PASSWORD. |
[PERMISSION_DENIED] ORA-01031 or empty dictionary results |
The account lacks SELECT on the object or SELECT_CATALOG_ROLE. Grant read access. |
[VALIDATION_FAILURE] Only SELECT … permitted |
The SQL isn't a lone SELECT (or contains a semicolon/comment). Send one clean SELECT. |
| Tool returns rows for several schemas | The object name exists in multiple visible schemas. Pass schema (or set ORACLE_DEFAULT_SCHEMA) to scope. |
| Agent sees no output but stderr has logs | Correct — logs go to stderr by design; stdout carries the MCP protocol only. |
| Server exits immediately on start | Read the stderr line — config validation prints exactly which env var is wrong (no secrets). |
License
MIT.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。