Read-only Model Context Protocol (MCP) server for Amazon DynamoDB — safe exploration, querying, and schema discovery.
Project description
ddb-explorer-mcp
Read-only MCP server for Amazon DynamoDB
Safe exploration, querying, and schema discovery from your AI coding assistant.
One-click install
Add ddb-explorer-mcp to your IDE in one click:
Or install from the command line:
# Smithery (auto-configures your client)
npx -y @smithery/cli install ddb-explorer-mcp --client cursor
# Claude Code CLI
claude mcp add ddb-explorer -- uvx ddb-explorer-mcp
# npm wrapper (delegates to uvx under the hood)
npx ddb-explorer-mcp
Why ddb-explorer-mcp?
Write operations are structurally not exposed. The LLM cannot call
PutItem, UpdateItem, DeleteItem, BatchWriteItem, TransactWriteItems,
CreateTable, DeleteTable, or UpdateTable because those tools are not
registered with the server. Not a config flag — they physically do not exist
in the tool namespace.
| Safe by construction | Read-only tool surface; no writes possible |
| Eight practical tools | list / describe / get / batch-get / query / scan + schema discovery helpers |
| Two transports | stdio (local) or streamable-HTTP (hosted) |
| Works everywhere | Cursor, Claude Desktop, VS Code, Windsurf, Zed, Claude Code CLI, any MCP client |
| Standard AWS creds | Env vars, profiles, SSO, IMDS all just work |
| Guardrails for scans | Default max_pages=1, hard caps on limits (all configurable via env vars), auto-pagination for query |
| Smart batch ops | batch_get_item auto-chunks into 100s and retries UnprocessedKeys |
| JSON-clean output | Decimal, set, bytes coerced to JSON-native types |
Installation
For end users (individual, stdio mode)
uvx ddb-explorer-mcp
That's it. uv downloads, caches, and runs the server
in an isolated environment on first use.
From a local clone
Clone the repo and the MCP server auto-registers in Cursor and VS Code:
git clone https://github.com/mudassirgithub/ddb-explorer-mcp.git
cd ddb-explorer-mcp
make setup # installs Python + all dependencies via uv
Open the cloned folder in your IDE:
-
Cursor — reads
.cursor/mcp.jsonautomatically. Restart Cursor, then check Settings → Tools & MCP. The server appears asddb-explorer. -
VS Code — reads
.vscode/mcp.jsonautomatically (native MCP support in VS Code 1.99+). No extension required. -
Claude Code CLI — register the local server manually:
claude mcp add ddb-explorer -- uv run --directory /path/to/ddb-explorer-mcp ddb-explorer-mcp
-
Claude Desktop / Windsurf / other clients — copy the config from
examples/cursor-local.jsonand replace the path with your clone directory.
Edit AWS_REGION / AWS_PROFILE in the relevant MCP config to match your
environment. Changes to the Python source are picked up on the next server
restart — no reinstall needed.
Makefile targets
| Target | What it does |
|---|---|
make setup |
Install Python + dependencies via uv sync |
make run |
Start the server in stdio mode |
make run-http |
Start the server in HTTP mode on localhost:8765 |
make test |
Run the test suite (uses moto — no real AWS creds needed) |
make lint |
Lint with ruff |
make format |
Auto-format with ruff |
make check |
Run lint + format-check + tests (same as CI) |
make clean |
Remove build/cache artifacts |
For teams (hosted HTTP mode)
docker run --rm -p 8765:8765 \
-e MCP_TRANSPORT=http \
-e MCP_HOST=0.0.0.0 \
-e AWS_REGION=us-east-2 \
-e AWS_ACCESS_KEY_ID=... \
-e AWS_SECRET_ACCESS_KEY=... \
ghcr.io/mudassirgithub/ddb-explorer-mcp:latest
See HTTP deployment below.
Client configuration
Ready-to-paste configs are also available in the examples/ directory.
Cursor
Add to ~/.cursor/mcp.json (global) or .cursor/mcp.json (per-project):
{
"mcpServers": {
"ddb-explorer": {
"command": "uvx",
"args": ["ddb-explorer-mcp"],
"env": {
"AWS_PROFILE": "default",
"AWS_REGION": "us-east-2"
}
}
}
}
Restart Cursor (⌘Q on macOS). Open Settings → Tools & MCP to verify.
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
{
"mcpServers": {
"ddb-explorer": {
"command": "uvx",
"args": ["ddb-explorer-mcp"],
"env": {
"AWS_PROFILE": "default",
"AWS_REGION": "us-east-2"
}
}
}
}
Claude Code CLI
claude mcp add ddb-explorer \
--env AWS_PROFILE=default \
--env AWS_REGION=us-east-2 \
-- uvx ddb-explorer-mcp
VS Code (with Cline, Continue, or native MCP)
Add to your VS Code settings.json:
{
"mcp": {
"servers": {
"ddb-explorer": {
"command": "uvx",
"args": ["ddb-explorer-mcp"],
"env": {
"AWS_PROFILE": "default",
"AWS_REGION": "us-east-2"
}
}
}
}
}
Windsurf
Add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"ddb-explorer": {
"command": "uvx",
"args": ["ddb-explorer-mcp"],
"env": {
"AWS_PROFILE": "default",
"AWS_REGION": "us-east-2"
}
}
}
}
Remote HTTP (any client)
{
"mcpServers": {
"ddb-explorer": {
"type": "streamableHttp",
"url": "https://ddb-explorer.example.com/mcp",
"headers": {
"Authorization": "Bearer <your-token>"
}
}
}
}
Tools reference
| Tool | Purpose |
|---|---|
list_tables(name_contains?) |
List tables in the region, optionally filtered by substring |
describe_table(table_name) |
Full description: keys, attrs, GSIs, LSIs, TTL, item count, size |
get_indexes(table_name) |
Trimmed view — just the secondary indexes |
sample_items(table_name, n?) |
Fetch a few items to discover the schemaless attribute shape |
get_item(table_name, key, consistent_read?) |
Single-item read by primary key |
batch_get_item(table_name, keys[], consistent_read?) |
Many items by PK, auto-chunked into 100s, retries UnprocessedKeys |
query(table_name, key_condition_expression, expression_attribute_values, ...) |
Query base table or GSI/LSI with full DynamoDB expression syntax |
scan(table_name, filter_expression?, max_pages?, ...) |
Scan with safe pagination caps (default max_pages=1, hard cap 20) |
Example prompts
"List all tables with
orderin the name and describe the newest one."
"Query
prod-orderswhereuser_id = 'U123'andcreated_at > '2026-01-01', using theby_user_createdGSI, limit 20."
"Sample 5 items from
prod-eventsand tell me which attributes are always present."
"Compare the schemas of
prod-ordersandstaging-orders— are there attributes in one that don't exist in the other?"
See examples/PROMPTS.md for more.
Configuration
All configuration is via environment variables.
AWS credentials / region
The server uses the standard boto3 credential resolution chain. The first valid source wins:
AWS_ACCESS_KEY_ID+AWS_SECRET_ACCESS_KEY(+ optionalAWS_SESSION_TOKEN) env varsAWS_PROFILEpointing to a profile in~/.aws/credentialsor~/.aws/config- AWS SSO cache
- IAM role attached to the execution context (EC2 instance profile, ECS task role, Lambda role)
| Variable | Default | Purpose |
|---|---|---|
AWS_REGION |
us-east-2 |
DynamoDB region |
AWS_DEFAULT_REGION |
— | Fallback for region |
AWS_PROFILE |
— | Named profile to use |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN |
— | Explicit credentials |
Transport
| Variable | Default | Purpose |
|---|---|---|
MCP_TRANSPORT |
stdio |
stdio | http | sse |
MCP_HOST |
127.0.0.1 |
HTTP bind host (use 0.0.0.0 to expose) |
MCP_PORT |
8765 |
HTTP bind port |
Safety limits
All tool safety caps are configurable via environment variables. Defaults match the original hardcoded values — existing deployments are unaffected unless you explicitly override them.
| Variable | Default | Purpose | Cost / data implications |
|---|---|---|---|
DDB_SCAN_MAX_LIMIT |
500 |
Hard cap on items per scan page | Most impactful. Scans read every item in the table. On large prod tables (millions of items), high limits burn RCUs and can return sensitive data into the LLM context. Consider 50–100 for production. |
DDB_SCAN_DEFAULT_LIMIT |
50 |
Default items per scan page (when caller omits limit) |
Safety net — keeps casual/unparameterized scans small. |
DDB_SCAN_MAX_PAGES |
20 |
Hard cap on pages per scan call | Combined with SCAN_MAX_LIMIT, this controls the maximum blast radius of a single scan. 20 pages × 500 items = 10,000 items worst case. Lower to 3–5 on production. |
DDB_SCAN_DEFAULT_MAX_PAGES |
1 |
Default pages per scan (when caller omits max_pages) |
Keep this at 1 for production. This single setting prevents the LLM from accidentally walking an entire table. |
DDB_QUERY_MAX_LIMIT |
500 |
Hard cap on items per query | Queries are targeted (use a partition key), so cost is lower than scans. But a hot partition could still have thousands of items. 100–200 is safer for production. |
DDB_QUERY_DEFAULT_LIMIT |
50 |
Default items per query | Safety net for unparameterized queries. |
DDB_SAMPLE_MAX_N |
20 |
Max items returned by sample_items |
Low risk — this is a schema discovery tool. Even at 20, cost is negligible. |
DDB_BATCH_CHUNK_SIZE |
100 |
Keys per BatchGetItem API call |
Matches the DynamoDB service maximum. Lowering this adds round-trips without saving cost. Leave at 100 unless you need to reduce per-request throughput spikes on provisioned tables. |
DDB_BATCH_MAX_RETRIES |
10 |
Max retries for UnprocessedKeys |
Only relevant under throttling. On provisioned-capacity tables, aggressive retries can amplify throttle pressure. On on-demand tables, this rarely triggers. |
DDB_SHOW_COST |
false |
When true, each data-plane tool response includes a consumed_capacity field with exact RCUs reported by DynamoDB |
Zero overhead when disabled. When enabled, uses DynamoDB's native ReturnConsumedCapacity — no extra API calls, no estimation. Covers get_item, batch_get_item, query, scan, and sample_items. Control-plane tools (list_tables, describe_table, get_indexes) are always free and never report capacity. |
DDB_MAX_ITEMS_PER_SESSION |
0 (unlimited) |
Cumulative cap on total items returned across all tool calls in a server session | Prevents data exfiltration by bounding how much data can be extracted before a server restart. Set to 500–2000 for production. Once exceeded, all data-plane calls return an error until the server is restarted. |
DDB_MAX_CALLS_PER_MINUTE |
0 (unlimited) |
Max tool calls allowed per rolling 60-second window | Prevents rapid-fire automated extraction. Applies to all 8 tools. Rejected calls return a RateLimitExceeded error. Set to 30–60 for production. |
DDB_AUDIT_LOG |
false |
When true, every tool call is logged to stderr with timestamp, tool name, table, and item count |
Enables post-incident investigation and real-time monitoring. Logs go to stderr so they don't interfere with MCP's stdio transport. Pair with a log aggregator in HTTP mode. |
DDB_ALLOWED_TABLES |
(empty = all) | Comma-separated list of allowed table names/patterns (supports * globs) |
App-level table restriction independent of IAM. list_tables only returns matching tables; all other tools reject non-matching tables. Examples: dev-*, dev-UserProfiles,dev-Orders, dev-*,staging-*. |
DDB_MAX_RESPONSE_BYTES |
0 (unlimited) |
Cap the serialized JSON size of a single tool response | Prevents a few very large items (DynamoDB allows up to 400 KB/item) from token-bombing the LLM context window. When exceeded, items are progressively dropped and a truncated flag is added. For get_item (single item, no list), an error is returned instead. Set to 500000–1000000 for production. |
DDB_READ_TIMEOUT |
10 (seconds) |
boto3 read timeout for every DynamoDB API call | Prevents slow or hung queries from tying up server resources (DoS vector in HTTP mode). A ReadTimeoutError is caught by the existing error handler and returned cleanly. Lower to 5 in latency-sensitive deployments; raise to 30 if scanning very large pages. |
Quick cost reference
| Scenario | RCUs consumed | Approximate cost |
|---|---|---|
| Scan 50 items × 1 KB (1 page, default settings) | ~13 | $0.000003 |
| Scan 500 items × 1 KB (1 page, max limit) | ~125 | $0.00003 |
| Scan 500 items × 20 pages × 1 KB (max everything) | ~2,500 | $0.0006 |
| Full table scan of 1M items × 1 KB | ~250,000 | $0.06 |
| Query 50 items × 1 KB | ~13 | $0.000003 |
| BatchGetItem 100 items × 1 KB | ~25 | $0.000006 |
Costs shown are for eventually consistent reads at $0.25 per million RCUs (on-demand pricing, us-east-2). Strongly consistent reads cost 2×.
Beyond DynamoDB cost, large result sets also increase LLM token usage — 500 items serialized as JSON can easily exceed 50K tokens.
Example: tightened production config
{
"mcpServers": {
"ddb-explorer": {
"command": "uvx",
"args": ["ddb-explorer-mcp"],
"env": {
"AWS_PROFILE": "prod-readonly",
"AWS_REGION": "us-east-2",
"DDB_SCAN_DEFAULT_LIMIT": "20",
"DDB_SCAN_MAX_LIMIT": "100",
"DDB_SCAN_DEFAULT_MAX_PAGES": "1",
"DDB_SCAN_MAX_PAGES": "3",
"DDB_QUERY_MAX_LIMIT": "200",
"DDB_SHOW_COST": "true",
"DDB_MAX_ITEMS_PER_SESSION": "1000",
"DDB_MAX_CALLS_PER_MINUTE": "30",
"DDB_AUDIT_LOG": "true",
"DDB_ALLOWED_TABLES": "prod-orders,prod-users,prod-events",
"DDB_MAX_RESPONSE_BYTES": "500000",
"DDB_READ_TIMEOUT": "10"
}
}
}
}
HTTP-mode deployment
HTTP mode is for hosted / team scenarios. A single server instance serves many remote clients; the server holds the AWS credentials, clients don't.
What the server does NOT provide
- TLS — terminate at a reverse proxy (nginx, Caddy, ALB, CloudFront)
- Authentication — enforce at the proxy (Basic auth, JWT, mTLS, API Gateway + IAM, OAuth2)
- Rate limiting — handle at the proxy or a sidecar
Recommended production shape
Internet ──► ALB (TLS + OIDC/JWT)
│
└─► ECS Fargate task
Image: ghcr.io/mudassirgithub/ddb-explorer-mcp:latest
Env: MCP_TRANSPORT=http, MCP_HOST=0.0.0.0
IAM: task role with read-only DynamoDB policy
A ready-to-use docker-compose.yml is available in examples/.
Minimal IAM policy for the server's AWS identity
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:DescribeTable",
"dynamodb:DescribeTimeToLive",
"dynamodb:GetItem",
"dynamodb:BatchGetItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:ListTables"
],
"Resource": "*"
}
]
}
For extra isolation, scope Resource to specific table ARNs.
Quick local HTTP test
MCP_TRANSPORT=http MCP_PORT=8765 uvx ddb-explorer-mcp
Then in another terminal:
curl -i http://127.0.0.1:8765/mcp
# Expect HTTP 406 — endpoint is live; MCP needs proper JSON-RPC POST + Accept.
Connect an MCP client pointing at http://127.0.0.1:8765/mcp to actually exercise it.
Security model
- Read-only by construction. No write tools are registered with the MCP server. This is a structural property, not a runtime flag.
- No tool masquerading. Tool signatures are fixed; the server does not dynamically execute arbitrary DynamoDB API calls on behalf of the LLM.
- Scan safety.
scandefaults tomax_pages=1(one page only). Hard cap 20 pages.querypaginates internally with alimit; hard-capped at 500 items per call. All caps are configurable via environment variables — see Safety limits. - Session item cap.
DDB_MAX_ITEMS_PER_SESSIONbounds the total items a single server session can return across all tool calls. Once exceeded, all data-plane calls are rejected until the server is restarted. Prevents automated exfiltration loops. - Rate limiting.
DDB_MAX_CALLS_PER_MINUTEenforces a sliding-window call rate across all tools. Prevents rapid-fire extraction or accidental runaway loops. - Table allowlist.
DDB_ALLOWED_TABLESrestricts which tables the server can access, independent of IAM permissions. Supports glob patterns (e.g.dev-*).list_tablesonly returns matching tables; all other tools reject non-matching ones. - Audit logging.
DDB_AUDIT_LOG=truelogs every tool call to stderr with timestamp, tool name, table, and item count. Useful for monitoring and post-incident investigation, especially in HTTP mode with a log aggregator. - Response size cap.
DDB_MAX_RESPONSE_BYTESlimits the serialized JSON size of any single tool response. Large items (DynamoDB allows up to 400 KB each) can overwhelm the LLM context window and spike token costs. When exceeded, items are progressively dropped and atruncatedflag is set. - DynamoDB call timeout.
DDB_READ_TIMEOUT(default 10 s) prevents slow or hung queries from tying up server resources. Applies to every boto3 call viabotocore.config.Config. Timeout errors are caught and returned cleanly. - No credential exposure to the LLM. Credentials live in the server process's environment or IAM role. The LLM sees tool results, never config.
- Prompt injection risk. DynamoDB items are user-generated data. A malicious
item could contain text designed to influence LLM behavior (e.g. "ignore
previous instructions and..."). This is inherent to any system that feeds
untrusted data into an LLM. Mitigations: restrict table access via
DDB_ALLOWED_TABLESto trusted tables, enable human-in-the-loop approval for tool calls in your MCP client, and reviewDDB_AUDIT_LOGoutput for unexpected access patterns. - HTTP mode isolation. In HTTP mode (
MCP_TRANSPORT=http), rate limits (DDB_MAX_CALLS_PER_MINUTE) and session caps (DDB_MAX_ITEMS_PER_SESSION) are per-server-process, not per-client. One client's usage counts against all clients sharing the same server instance. For multi-tenant isolation, run separate server instances per client/team, or proxy through an API gateway that enforces per-client quotas. The server's internal counters are thread-safe but intentionally shared to provide a global safety net. - Defense in depth. Even with the above guarantees, you should still attach
the server's AWS identity to a read-only DynamoDB IAM policy. Never give it
write or admin permissions, ever. Scope
Resourcein the IAM policy to specific table ARNs for maximum isolation.
Development
git clone https://github.com/mudassirgithub/ddb-explorer-mcp.git
cd ddb-explorer-mcp
make setup # one-time: installs Python + deps
make test # run the full test suite
make check # lint + format-check + tests (same as CI)
make run # start the server in stdio mode
Or without make:
uv sync --extra dev
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run ddb-explorer-mcp
Contributing
Contributions welcome — see CONTRIBUTING.md.
Code of Conduct
This project follows the Contributor Covenant.
Security
To report a security vulnerability, see SECURITY.md. Please do not open public issues for security matters.
License
Apache License 2.0 — see LICENSE.
Trademark notice
This project is not affiliated with, endorsed by, or sponsored by Amazon Web Services, Inc. "AWS" and "Amazon DynamoDB" are trademarks of Amazon.com, Inc. or its affiliates.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ddb_explorer_mcp-0.1.1.tar.gz.
File metadata
- Download URL: ddb_explorer_mcp-0.1.1.tar.gz
- Upload date:
- Size: 32.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d677d3c64b16f4b2fc020cf582a3c313d4afbcc0346034f39188fe7192408ebc
|
|
| MD5 |
fc4a64522c98ff1506d0270478a3433b
|
|
| BLAKE2b-256 |
ee200d4ed1a0a5024a657c9d2fe87cc22136618a8fc611784b7b49af2db59707
|
Provenance
The following attestation bundles were made for ddb_explorer_mcp-0.1.1.tar.gz:
Publisher:
semantic-release.yml on mudassirgithub/ddb-explorer-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ddb_explorer_mcp-0.1.1.tar.gz -
Subject digest:
d677d3c64b16f4b2fc020cf582a3c313d4afbcc0346034f39188fe7192408ebc - Sigstore transparency entry: 1810718839
- Sigstore integration time:
-
Permalink:
mudassirgithub/ddb-explorer-mcp@4e71a71daee5c5cd501ea70351f6eea3bc637c60 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/mudassirgithub
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
semantic-release.yml@4e71a71daee5c5cd501ea70351f6eea3bc637c60 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ddb_explorer_mcp-0.1.1-py3-none-any.whl.
File metadata
- Download URL: ddb_explorer_mcp-0.1.1-py3-none-any.whl
- Upload date:
- Size: 23.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6b1f3ac8a35ec047ea365c961c4a09377b65ab2e10711506af938a0117e48744
|
|
| MD5 |
37fc331bc845452964d6d87ac4b6e2da
|
|
| BLAKE2b-256 |
0741feff7c8c533d750e3c39a0d029235e2dfdd540123ef95ef5f92e0ebee456
|
Provenance
The following attestation bundles were made for ddb_explorer_mcp-0.1.1-py3-none-any.whl:
Publisher:
semantic-release.yml on mudassirgithub/ddb-explorer-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ddb_explorer_mcp-0.1.1-py3-none-any.whl -
Subject digest:
6b1f3ac8a35ec047ea365c961c4a09377b65ab2e10711506af938a0117e48744 - Sigstore transparency entry: 1810719218
- Sigstore integration time:
-
Permalink:
mudassirgithub/ddb-explorer-mcp@4e71a71daee5c5cd501ea70351f6eea3bc637c60 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/mudassirgithub
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
semantic-release.yml@4e71a71daee5c5cd501ea70351f6eea3bc637c60 -
Trigger Event:
push
-
Statement type: