DynamoDB and OpenSearch CLI for AI agents
dynotable-cli lets an AI agent, such as Claude Code, Codex or Cursor, read your DynamoDB tables
and OpenSearch indices through the DynoTable app on your computer. The agent runs a command and
gets JSON back.
You stop pasting query results into the chat, and the agent never gets your AWS keys. The CLI only reads, and it sees only the profiles that you make available to agents.
What you need
- DynoTable 1.20.0 or later, installed on the same computer.
- Editor integration switched on in Settings → Editor integration. It is off by default. See Turn on Editor integration.
- One or more profiles that are available to agents.
Make a profile available to agents
- In DynoTable, open Settings and select the profile.
- Open the profile's MCP section.
- Switch on Available to agents.
Do this for each profile that agents can read. dynotable-cli cannot list or read the other
profiles.
A profile that is not available and a profile that does not exist give the same error, so an agent cannot find out which other profiles you have.
The same switch controls the MCP server. When you switch it off, a running agent loses the profile at its next request.
Install dynotable-cli
- npm:
npm install -g dynotable-cli. This also installsdynotable-lsp. - GitHub Releases: download the archive for your platform from
Releases, extract it, and put
dynotable-clianddynotable-lspon yourPATH. The macOS archive is not notarized yet, so macOS blocks it after a browser download. On macOS, use npm. - Windows: use the Releases archive and put the folder that holds
dynotable-cli.exeon yourPATH. The Claude Code plugin cannot start the command that npm installs on Windows.
Builds are available for macOS on Apple silicon, Linux x64 and Windows x64. The macOS build is tested on every build. The Linux build has run in a container, and the Windows build has not run yet.
When DynoTable is not running, dynotable-cli starts it with no window, and the app keeps running
after the command ends. With --no-launch, the command fails with launcher-off instead.
Commands
dynotable-cli profiles # the profiles available to agents
dynotable-cli tables --profile dev # the DynamoDB tables of a profile
dynotable-cli indices --profile dev # the OpenSearch indices of a profile
dynotable-cli run orders.dtsql # run the statement in a filerun also reads a statement from stdin (-) or from the text after -e. Both need --lang sql,
--lang partiql or --lang opensearch:
dynotable-cli run -e "SELECT * FROM \"orders\" WHERE customerId = 'C1'" --lang partiql- Give one statement per input.
- A file must be a query file. The CLI refuses every other file.
--row-cap <n>sets the most rows thatrunreturns. The default is 500, and the maximum is 1000.truncated: truein the result means that more rows exist.- Writes do not run. A PartiQL write fails with
stage-in-app. Make the change in DynoTable, where staging holds it for review. dynotable-cli --helpshows all options.
Pick a profile
A command uses the first profile it finds:
--profile <id or slug>.- The
profilekey of.dynotable.jsonin the current directory. - The only profile available to agents, when there is exactly one.
{"profile": "dev"}dynotable-cli reads only the profile key.
Output and exit codes
Stdout holds one JSON document. On failure, stderr holds one JSON object with a code and a
message that tells what to do:
{
"code": "profile-not-exposed",
"message": "The profile is not found, or it is not available to agents. Make the profile available to agents in DynoTable."
}| Exit code | Meaning |
|---|---|
0 | Success. |
1 | Failure. Read code on stderr. |
2 | The command line is not valid (usage). |
130 | Interrupted with Ctrl+C. Stdout is empty. |
code | What to do |
|---|---|
profile-not-exposed | Make the profile available to agents in DynoTable. |
profile-required | Give --profile. dynotable-cli profiles lists the choices. |
stage-in-app | The CLI only reads. Make writes in DynoTable. |
reauth-required, signed-out | Open DynoTable and sign in. |
launcher-off, integration-off | Start DynoTable, or switch on Editor integration. |
agent-refused | The request is not available to agents. Do it in DynoTable. |
update-app | Update DynoTable. |
listing-failed | The listing is not complete. Stdout holds the rows it read. |
Query files
| File | Language |
|---|---|
*.dtsql | DynoTable SQL, over tables and indices |
*.dynotable.sql | DynoTable SQL, over tables and indices |
*.partiql | DynamoDB PartiQL |
*.opensearch | One OpenSearch query: JSON DSL or Lucene |
The file name gives the language, so do not give --lang with a file. An OpenSearch query names
its index on its first line:
// dynotable: index=orders
{"query": {"match": {"status": "shipped"}}}Install the Claude Code plugin
The plugin gives Claude Code a dynotable skill that runs dynotable-cli for you. It also gives
diagnostics and hover in .dtsql, .partiql and .opensearch files. It does not attach to
.sql files.
In Claude Code, add the marketplace and install the plugin:
/plugin marketplace add dynotable/claude-plugins /plugin install dynotable@dynotable
The plugin works while DynoTable is running with Editor integration on. It does not start DynoTable.
When the app is not running, the plugin's language server waits up to 20 seconds for it and then stops. If diagnostics stop after DynoTable was closed for a while, start a new Claude Code session.
Codex, Cursor and other agents that run shell commands use dynotable-cli directly. Only Claude
Code has a plugin.
Limit Claude Code to dynotable-cli
Add this rule to your Claude Code settings (~/.claude/settings.json for every project, or
.claude/settings.json in one project):
{
"permissions": {
"allow": ["Bash(dynotable-cli:*)"],
"deny": [
"Bash(dynotable-lsp:*)",
"Read(~/Library/Application Support/DynoTable/**)",
"Read(~/.config/DynoTable/**)",
"Read(~/AppData/Roaming/DynoTable/**)"
]
}
}The three Read paths are DynoTable's data folders on macOS, Linux and Windows. Keep the one for
your platform.
Every dynotable-cli command opens an agent session, which reaches only the profiles available to
agents. dynotable-lsp opens an editor session, which reaches every profile, so the rule denies
it.
Neovim and other editors
dynotable-lsp connects an editor that runs a language server over stdio to DynoTable. It is the
same program as dynotable-cli, under a second name.
Use it only in an editor, never in an agent. It can use every profile and writes no audit rows.
- It takes
--profile <id or slug>and--no-launch. - With no
--profile, it uses theprofileof the editor's initialization options, then.dynotable.jsonin the workspace root. - The editor must send the language ids
sql,partiqlandopensearch. - It starts DynoTable with no window when the app is not running.
Neovim 0.10 or later:
vim.filetype.add({
extension = { dtsql = 'sql', partiql = 'partiql', opensearch = 'opensearch' },
})
vim.api.nvim_create_autocmd('FileType', {
pattern = { 'sql', 'partiql', 'opensearch' },
callback = function(args)
local name = vim.api.nvim_buf_get_name(args.buf)
-- Serve only DynoTable's own SQL files, not every .sql file.
if vim.bo[args.buf].filetype == 'sql'
and not (name:match('%.dtsql$') or name:match('%.dynotable%.sql$')) then
return
end
vim.lsp.start({
name = 'dynotable',
cmd = { 'dynotable-lsp' },
root_dir = vim.fs.root(args.buf, { '.dynotable.json', '.git' }),
}, { bufnr = args.buf })
end,
})Other LSP editors can start dynotable-lsp the same way. Only Neovim is tested. For VS Code, use
the DynoTable extension.
What an agent can reach
- Only the DynoTable app on this computer.
dynotable-cliconnects to nothing else. - No AWS credentials. They stay in DynoTable, which runs each request with the profile's own credentials.
- Only the profiles available to agents. Switch a profile off and the next request fails.
- Reads only. Writes are staged and committed in the app, by you.
- A row in the audit log for every data request. Each
tables,indicesandrunrequest is in the profile's audit log, under Settings → profile → Activity. The row holds the statement, never the rows it returned. When DynoTable cannot write the row, the request fails and returns no data. - Nothing while Editor integration is off. Switching it off stops every agent session.
dynotable-cli refuses a DynoTable app older than 1.20.0 with update-app, and no data reaches
the agent.
The plugin's diagnostics and completion write no audit rows. They give the agent table, index and field names, never stored values.
A program that can run any command as you can also read DynoTable's files. Give an agent
dynotable-cli and nothing broader.