--- title: Write Support description: How to let the Actian MCP Server for NoSQL create, update, and delete objects, and the three checks — the setting, the mcp:write scope, and human confirmation — that authorize every write. --- # Write Support The Actian MCP Server for Actian NoSQL is read-only by default. Setting `nsql.writes.enabled` to `true` adds three tools that change data: `create_objects`, `update_objects`, and `delete_objects`. A deployment that never sets it is not affected by anything on this page. Turning writes on does not give the connected AI agent unrestricted access to the database. Every write clears three independent checks — the server setting, the caller's `mcp:write` scope, and a person confirming the operation — and each of them can stop it. This page covers all three, the properties that tune them, and what to do when the write tools do not appear in the client at all. For each tool's parameters, batch limits, and result shape, see [Tools](tools/index.md). Tools contributed by an [extension](extensions/index.md) pass the same three checks. !!! note "Writes are never expressed as JPQL" Enabling write mode does not change `execute_query`. It still accepts `SELECT` only, and still rejects anything that could modify state. Every mutation goes through one of the three write tools instead. !!! note "Write mode does not change the schema" The write tools create, change, and remove *objects*; they cannot add a class, alter a class, or change an index. Use the tools that come with Actian NoSQL Database for schema changes. ## Enabling Write Mode Set `nsql.writes.enabled` in `application.properties`: | Value | Behavior | |-------|----------| | `false` | Default. The write tools are hidden from clients, and any call to one is rejected. | | `true` | The write tools are registered, subject to the checks below. | ```properties nsql.writes.enabled=true ``` As with every property on this server, the environment-variable form is also supported — `NSQL_WRITES_ENABLED=true` — and takes precedence over the file. The server has no hot reload for this setting: restart it after changing the value. ## Authorizing a Write Authorization happens at two separate moments: the server decides which write tools a client may have when that client connects, and then vets each call as it arrives. ### Tools Provided at Connect Time A write tool is registered for a connection only when **both** of these hold: 1. `nsql.writes.enabled` is `true`; and 2. the client advertised the MCP elicitation capability, so the server has a way to ask a person for confirmation. The second condition is waived when `nsql.writes.confirmation-required` is `false` — with no prompt to show, there is no reason to withhold the tools from a client that could not show one. A tool the server withholds is unlisted **and** uncallable: calling it anyway fails at the protocol layer with an unknown-tool error, before any server-side logic runs. If the write tools are missing when you expected them, [Missing Write Tools](#missing-write-tools) is the troubleshooting guide. ### What Each Call Must Clear Every call that does reach the server passes three independent checks, in the order below — so a caller on a read-only server, or one whose token lacks the scope, is turned away before anybody is asked to confirm anything. | Check | What it requires | When it applies | |-------|------------------|-----------------| | `nsql.writes.enabled` | The setting must be `true`. | Always. Re-checked here as a backstop, having already governed registration. | | `mcp:write` scope | The access token must carry the `mcp:write` scope. | Only when authentication is enabled (`mcp.auth.enabled=true`). An unauthenticated server does not check scopes. | | Human confirmation | A person must confirm the operation in the connected client. | Always, unless you disable it with `nsql.writes.confirmation-required`. | The sequence below picks up at this second stage, with the tool already registered: ```mermaid %%{init: {'theme': 'default', 'themeVariables': {'fontSize': '18px', 'fontFamily': 'arial'}}}%% sequenceDiagram participant User as User participant Client as MCP Client participant Server as MCP Server participant DB as Actian NoSQL Database Client->>Server: Write request (create / update / delete objects) Server->>Server: Check nsql.writes.enabled alt Writes disabled Server-->>Client: Rejected (write operations are disabled) else Writes enabled Server->>Server: Check mcp:write scope alt Scope missing Server-->>Client: Rejected (missing scope) else Scope present or authentication disabled Server->>Client: Request confirmation Client->>User: Show the operation and the object count alt User confirms User-->>Client: Confirm Client-->>Server: Confirmed Server->>DB: Run one atomic transaction DB-->>Server: Committed Server-->>Client: Result with the affected LOIDs else Declined, cancelled, no response, or client cannot prompt Client-->>Server: Not confirmed Server-->>Client: Rejected (write not performed) end end end ``` To grant the `mcp:write` scope in your identity provider, see [Auth0](authentication/auth0/index.md) or [Keycloak](authentication/keycloak/index.md). !!! note "The server checks the scope; it never asks for it" Nothing on the server requests `mcp:write` — it only reads whatever the presented token carries, so the request has to come from the MCP client. A client that lets you set its scopes can be told to ask for it directly. One that works them out from the server's resource metadata asks for whatever is advertised there, which is a server setting: see [Advertising scopes to MCP clients](authentication/index.md#advertising-scopes-to-mcp-clients). !!! warning "The scope is the only per-caller write control" The server authenticates to Actian NoSQL Database with the credentials carried in its connection URL, so every statement it runs — read or write — runs as that one configured database user. There is no per-user impersonation, and so no database-privilege layer underneath that could narrow what an authorized caller may change. `mcp:write` is all or nothing: a caller who holds it can create, update, or delete objects of any class in the database. Grant `mcp:write` only to the callers that should be able to modify data, and treat the database user in `nsql.connectionURL` as the true limit of what any caller can reach. ## Confirmation Prompts The server requests confirmation through the Model Context Protocol (MCP) elicitation capability, and enforces the answer server-side. The write tool does not run until an explicit acceptance comes back — the client is never trusted to gate the operation on the server's behalf. The prompt is a summary, not a transcript. It says what the operation will do and how many objects it affects, and stops there — field values and individual LOIDs are deliberately left out, so a hundred-object batch stays as readable as a one-object batch. Each tool's exact wording is shown with that tool in [Tools](tools/index.md). Only an explicit confirmation lets the write proceed. Every other outcome rejects it: | Outcome | Result | |---------|--------| | The user confirms | The write runs. | | The user declines | Rejected. | | The user cancels | Rejected. | | The prompt is submitted without confirming | Rejected. | | No response within `nsql.writes.confirmation-timeout-seconds` (default `60`) | Rejected. | | The client cannot display prompts at all | Rejected. | Nothing is written in any of those cases. The rejection message names the reason, so a declined write is distinguishable from one that timed out. ### Prompt Timeout The server waits a fixed window for an answer. The clock starts when the prompt is sent and runs for `nsql.writes.confirmation-timeout-seconds`, 60 by default. Silence is not consent: when the window closes the write is rejected outright, and the tool call returns naming the reason. ```text Create not performed: no confirmation was received within the timeout. ``` The server records the same event: ```text WARN Confirmation not received within 60s; rejecting operation. ``` Sixty seconds suits someone watching the conversation as it happens. Raise it when approvals go to a person who may be away from the screen. The tool call stays open for the whole wait, so the agent making the request is blocked until somebody answers or the timeout closes it. Lower it if you would rather a batch fail fast than sit pending. The timeout plays no part when `nsql.writes.confirmation-required` is `false`, because no prompt is sent. Not every MCP client implements elicitation. For which clients can display the prompt, see [Connecting MCP Clients](../mcp-clients/index.md#elicitation-support-for-write-approval). ## Missing Write Tools Write tools go missing because one of the two [registration conditions](#tools-provided-at-connect-time) was not met. Withholding them is deliberate: a tool the server could never get confirmed would fail on every call, so a client that cannot confirm simply never sees one. The startup log tells you which condition to look at. | What you see | Cause | What to do | |--------------|-------|------------| | No write tools, and the startup log says `Mode: READ-ONLY` | `nsql.writes.enabled` is `false`. | Set it to `true` and restart the server. | | No write tools, but the startup log says `Mode: READ-WRITE` | The connected client did not advertise elicitation. | Connect a client that supports it, or set `nsql.writes.confirmation-required=false`. | | A write tool call fails with a protocol error naming the tool, such as `-32602 Invalid tool name` | The tool is not registered for this connection, for one of the two reasons above. The MCP runtime rejects the call as unknown; it never reaches the server's write logic. | Fix the cause of the hiding, then reconnect the client so it re-reads the tool list. | | A write tool call is rejected for a missing scope | The access token does not carry `mcp:write`. | Grant the scope in your identity provider. | A hidden tool is unlisted **and** uncallable, so the usual sign of trouble is a protocol-level error about an unknown tool rather than a message from the server explaining itself. The server's own refusal, `Write operations are disabled on this server.`, is a backstop for a call that somehow bypasses the tool list; in normal use nobody sees it. Visibility is decided per connection, from the capabilities the client advertised when it connected. After changing either setting, restart the server and reconnect the client. This rule is not tied to specific tool names. It applies to any tool that does not declare itself read-only, including tools contributed by an [extension](extensions/index.md). ## Answering the Prompt from a Python Client A [FastMCP](https://pypi.org/project/fastmcp/) client advertises the elicitation capability only when it is given an elicitation handler. Without one it is a client that cannot confirm, so the write tools are never registered for it — the symptom described above, reached through your own code rather than through a third-party client. The handler receives the server's message and returns the answer. The prompt declares one required boolean property, `confirm`, which the server validates: an accepted prompt that does not carry `confirm` set to `true` counts as unconfirmed, and the write is refused. ```python from fastmcp import Client from fastmcp.client.elicitation import ElicitResult async def confirm_write(message, response_type, params, context): print(f"\nThe server requests approval:\n{message}") # input() runs on a worker thread so the connection stays responsive while it waits answer = await asyncio.to_thread(input, "Approve this write? [y/N] ") if answer.strip().lower() == "y": return ElicitResult(action="accept", content={"confirm": True}) return ElicitResult(action="decline") async with Client(transport, elicitation_handler=confirm_write) as client: ... ``` The only clock is the server's. FastMCP sets no request timeout unless you pass one, so leave it unset on a client that waits for a person — a client timeout shorter than [the confirmation window](#prompt-timeout) expires first and reports itself instead of the server's refusal. A complete, runnable version is available as [`nosql_hitl_client.py`](https://github.com/ActianCorp/mcp-server-docs/blob/main/examples-nosql/nosql_hitl_client.py), a console client that calls any tool and asks you to approve the write at the terminal: ```bash pip install fastmcp python nosql_hitl_client.py http://localhost:8080/mcp create_objects \ '{"className": "Employee", "objects": [{"name": "Ada Lovelace"}]}' ``` The client takes any tool with any arguments and knows nothing about your schema; `Employee` and the field names above are placeholders, so substitute your own. The [examples README](https://github.com/ActianCorp/mcp-server-docs/blob/main/examples-nosql/README.md) has the full usage. ## Skipping the Confirmation Prompt Some clients cannot display prompts. For those deployments, set `nsql.writes.confirmation-required` to `false`: ```properties nsql.writes.enabled=true nsql.writes.confirmation-required=false ``` !!! warning "This removes human oversight of every write" With `nsql.writes.confirmation-required` set to `false`, the server creates, updates, and deletes objects as soon as it is asked. Nobody is asked first. Use it only when the client cannot prompt and you accept that the AI agent writes unattended. The other two checks still apply. Disabling confirmation does not enable writes, and it does not grant write access to callers that lack the `mcp:write` scope. This setting is deployment-wide: it applies to the built-in write tools **and** to any extension tool that asks for confirmation. Both share the same confirmation mechanism, so turning it off silences both. The setting is visible in the log. At startup the server records the mode it is running in, and it raises that line to a warning when writes are unconfirmed: ```text WARN nsql-mcp-server Mode: READ-WRITE. Write tools are exposed; writes execute without confirmation. ``` ## Optimistic Concurrency Every object a read returns carries a `version` field: an opaque token that changes whenever the object is modified. It is returned as a string so that large values survive JSON without losing precision — the same reason 64-bit integer fields are, as described under [Response format](tools/index.md). Do not parse it or compare it for ordering — the only thing to do with it is hand it back. `update_objects` requires it. Each update item carries the target `loid`, the `expectedVersion` you read earlier, and the `fields` to change: ```json { "loid": "135.0.2146", "expectedVersion": "8273401", "fields": { "department": "Research" } } ``` The update is applied only if the object has not changed since you read it. If it has, the call fails — and because a write is one all-or-nothing transaction, a single stale version fails the whole batch, leaving every object in it untouched. Re-read the affected objects to obtain their current `version`, then retry. See [Tools](tools/index.md) for where `version` appears in each read tool's response. ## Configuration Reference | Property | Default | Description | |----------|---------|-------------| | `nsql.writes.enabled` | `false` | Master switch for the write tools. While `false` they are hidden from clients and every call to one is rejected. | | `nsql.writes.max-batch` | `100` | Maximum number of objects a single write call may touch in one transaction. A larger batch is rejected; split it. | | `nsql.writes.confirmation-required` | `true` | Whether a write must be confirmed by a person. Deployment-wide: applies to built-in and extension write tools alike. | | `nsql.writes.confirmation-timeout-seconds` | `60` | How long the server waits for a confirmation before rejecting the write. | ## Next Steps
- :material-tools: **[Tools](tools/index.md)** The three write tools, their parameters, batch limits, and result shapes. - :material-puzzle: **[Extensions](extensions/index.md)** Add your own tools, including write tools, subject to these same checks. - :material-lock: **[Authentication](authentication/index.md)** Enable OAuth 2.0 and grant the `mcp:write` scope. - :material-connection: **[MCP clients](../mcp-clients/index.md#elicitation-support-for-write-approval)** Which clients can display the confirmation prompt.