MCP server

Permissions and refusals

The read-only, writes, and production-writes ladder, what production means, and every refusal an agent can be handed.

Dynomatic decides every call from two things: what the call does to your data, and what you have allowed. Nothing else, and nothing an agent can send, changes the answer.

What a call does

Every tool and every command is classified before a target exists.

  • A read reads DynamoDB or reads Dynomatic’s own data. It works at every rung.
  • A local write changes Dynomatic’s own database and never reaches AWS. Saving a query, storing an inferred data model, importing a schema and starting an export are local writes. The target’s environment does not constrain them, because nothing in your account changes.
  • A remote write reaches your AWS account.

The ladder

Read-only, the default, admits reads and refuses both kinds of write.

Allow writes admits local writes, and admits remote writes to a target the environment classifier calls staging or development.

Allow production writes admits remote writes to every target, including the ones classified production and the ones with no classification.

Writes-off wins. If you turn Allow writes off while Allow production writes is still on, the server is read-only. Your production choice is kept, not cleared, so turning writes back on restores it.

The ladder is read fresh on every call, so a switch you flip applies to the next call an agent makes, including a call inside a script that is already running.

What production means here

The environment of a target is whatever the classification engine says, and an agent cannot change it. The commands that write a classification are withheld from the protocol outright, because a production verdict is the evidence a write is judged on.

A remote write under Allow writes is refused on two kinds of target.

  • A table, or a profile, classified Production.
  • A target Dynomatic could not verify. That covers a target with no classification row at all, and a table with no row of its own inside a profile that mixes environments.

An unverified target is refused as firmly as a production one, and that is deliberate. An unknown environment is not evidence that a table is safe to write to. Turning on Allow production writes is you saying “all of them”, including the ones nobody has labeled.

The environment_report tool returns every profile and table Dynomatic has classified with its environment and confidence, so an agent can tell you in advance which writes will go through and ask you to classify a table rather than retrying against a wall.

Scripts check every table they touch

run_script clears the ladder for the table you name, and then clears it again for every table the script’s own DynamoDB calls name, on every call. Nothing stops a script naming a second table on its next line, so the clearance the tool granted is not the clearance those calls run under. A refused call fails inside the script the way an AWS error would.

Naming no table checks the whole profile, which a profile holding both production and non-production tables fails, so naming the table you mean is the way to get a narrower answer.

PartiQL is classified by its statement

SELECT is a read and works while the server is read-only. INSERT, UPDATE and DELETE are writes and go through the production check over the table the statement itself names. Anything else is refused rather than guessed at, because calling a CREATE a read would run it at the read-only rung.

The statement is rendered before it is classified, so a ${[ … ]} template resolves first and the verdict covers the table the variable resolves to. A table argument naming a different table from the statement is refused.

Every refusal an agent can get

A refused call is a normal result marked as an error, carrying a refusal object with the kind, what happened, and what to change. An agent branches on kind rather than reading prose.

Kind What happened What fixes it
WritesDisabled The call writes and the server is read-only Turn on Allow writes
ProductionRefused The target is classified production and production writes are off Turn on Allow production writes, or target a non-production table
TargetUnverified The target’s environment could not be established Classify it in Dynomatic, or turn on Allow production writes
Gated The tool needs a plan feature this Dynomatic does not have Upgrade the plan, or use Sample mode
UnknownProfile No profile has that name. The answer lists the names that exist Call list_profiles and use one of its names exactly
UnknownCommand No command has that name. The answer lists the nearest ones Call list_commands and use one of its names exactly
NotHeadless The command cannot run over the protocol. A reason says which kind of cannot Depends on the reason, below
NotSearchable semantic_search cannot search that table the way the call asked. A reason says which of six ways, and attribute or attributes names what it was about Depends on the reason, below
AiConsentRequired The call would send your text to an embedding provider, and you have not agreed to send this account’s text to that provider. provider names which one Run one Vector search or embedding dry run in Dynomatic and answer Send item text to AI?. Only you can answer it
UnsupportedStatement The PartiQL statement’s leading keyword is none of the four Use SELECT, INSERT, UPDATE or DELETE
OwnershipFailed Dynomatic could not verify which profile owns the workspace at startup, so local writes are off for the session Restart Dynomatic with the AWS config readable
Busy Something Dynomatic runs one at a time is already running, such as a script Wait for it or cancel it, then retry
NoSession The call starts work that outlives it and the connection carries no session to own it Initialize a session and send its id. Most clients do this already
UnknownJob No job answers to that id Use the id export_start returned. A finished job is dropped after an hour
TooManyJobs The background job registry is full of running work Cancel a running job or wait for one to finish
TooLarge The request carries more items than the per-call ceiling Split it into smaller batches
RateLimited Too many calls from this session. The answer says how many milliseconds to wait Wait that long and retry
Timeout The call did not finish inside 60 seconds Answer any prompt Dynomatic is showing, then retry

NotSearchable and AiConsentRequired both come from semantic_search and from nothing else, so a client that never searches by meaning never sees either one. Three refusals, and what each one asks for has a real NotSearchable beside both shapes of WritesDisabled.

The NotHeadless reasons

NotHeadless carries one of six reasons, because “this command cannot run here” has six different fixes.

  • NeedsWebview drives the Dynomatic window or your clipboard.
  • NeedsChannel streams its progress into the window and has nowhere to send it. Background exports are these, and they have their own tools instead.
  • NeedsDialog opens a file dialog and waits for a person.
  • Withheld means somebody read the command and decided an agent must not run it. Nothing an agent can change.
  • Unclassified means nobody has decided yet, so it refuses rather than guessing.
  • NoWindow means the command would run but Dynomatic has no window open to show the result in.

Any command, through one tool covers what is withheld and why.

The NotSearchable reasons

NotSearchable carries one of six reasons, and each one names what the next call needs.

  • NoRecipe means the table embeds nothing at all. Create a recipe under Table settings, then Embeddings, and run its backfill, or read the table with query or scan instead.
  • AttributeRequired means the table embeds more than one attribute, so attributes lists them and the call has to pass attribute as one of them.
  • UnknownAttribute means the attribute the call passed is not one this table embeds. attributes lists the ones it does.
  • NoIndex means the named attribute is embedded but the table carries no vector index over it, so the vectors exist with nothing to search them by.
  • PartitionRequired means the index partitions on attribute and SearchVectors searches one partition at a time, so the call has to pass partition with a value of it.
  • PartitionRejected means the index partitions on nothing, so there is no partition to narrow to. Retry without partition.

A reason is the part to branch on. The kind alone does not say whether the fix is a call the agent can correct on its own, as AttributeRequired and PartitionRequired are, or something only you can do in the app.

What a write changes in the window

An agent’s write goes down the same path your own does, so it obeys the same plan limits, resolves the same template variables and uses the same credentials. What a write does to an open grid covers which tabs refresh and which do not.

Last updated on