Skip to main content
Version: 1.0.0 (development)

MCP Control: supervised mutations

rocketmq-mcp-control is an independent HTTPS MCP server for nine typed RocketMQ mutations. It is separate from read-only MCP: adding a query-MCP planning feature does not enable these tools. The default Control build contains no Admin Core dependency and no production mutation tools. Actual use requires the build capability, runtime policy, authenticated claims, configured cluster and durable audit path to agree.

Supported operations and boundaries​

For independent HTTPS, issuer CA, and outbound remoting TLS configuration, follow the TLS guide.

MCP toolClosed policy operationScope and important constraint
rocketmq_upsert_topictopic_upsertComplete Topic replacement on 1–64 explicit logical broker masters; queue counts 1–127, supported permission and message-type values
rocketmq_upsert_consumer_groupconsumer_group_upsertComplete Consumer Group replacement on 1–64 explicit logical broker masters; system groups are rejected
rocketmq_reset_consumer_offsetconsumer_offset_resetTopic/group and timezone-qualified RFC3339 timestamp; seals at most 1000 broker/queue targets and uses expected-offset CAS
rocketmq_patch_broker_configbroker_config_patchExactly one logical Broker and a nonempty patch restricted to six known properties
rocketmq_set_consumer_request_modeconsumer_request_modeTopic/group, pull or pop, nonnegative shared-queue count and timeout 1–24000 ms
rocketmq_begin_proxy_drainproxy_drain_beginOne Proxy, named by an alias from the server configuration; it stops admitting new work under a caller-chosen operation_id
rocketmq_cancel_proxy_drainproxy_drain_cancelEnds the drain that the same operation_id began; a drain held by another operation is a conflict
rocketmq_set_broker_log_filter_ttlbroker_log_filter_setOne rocketmq_broker:: logger on one logical Broker at INFO or DEBUG for 60–900 s; never replaces an active override
rocketmq_restore_broker_log_filterbroker_log_filter_restoreEnds the override that the same operation_id set; the Broker also ends it by itself when the time runs out

Broker patches accept only autoCreateTopicEnable, autoCreateSubscriptionGroup, brokerPermission, defaultTopicQueueNums, messageIndexEnable and traceTopicEnable. properties must be a JSON object. Boolean strings must be lowercase; null/empty values and unknown keys are rejected. See the complete tool schemas and examples for required fields and exact limits.

Proxy drains and temporary log levels are operation-bound. The caller chooses an operation_id of 8–64 letters, digits, _ and - when it starts the state and passes the same one to end it. Each call reads its one target first, sends at most one write and reads the target again. A target that another identifier holds is reported as conflict and is never replaced. A result shows the holder only as none, this_operation or other_operation, and no response, log line or audit record repeats an operation_id, so keep it in your own change record. A Proxy is named by an alias listed under [[clusters.proxies]] in the server configuration, never by an address. Loggers of modules that handle message content, credentials or storage are rejected.

There is no delete, skip, resend, arbitrary Admin command, shell, subprocess, free-form RPC or stdio transport. The optional write-tools feature enables only Admin Core's mutation-client adapter, not a read/full adapter. Necessary preflight/post-read observations belong to that typed mutation session; they do not create a general query surface.

Authorization, audit and session order​

OAuth and operation/cluster authorization complete before mutation argument parsing. A durable started record completes before session creation or RPC. The owned supervisor keeps the acquired session through bounded shutdown even if the caller disconnects, cancels or times out. A successful transport response alone does not describe the target effect.

Every operation validates the complete selected-cluster topology before target state RPCs. Conditional changes use the sealed target/session and are not retried by resolving a new route after conflict. Topic upsert treats the complete NameServer order-Topic KV as a no-write guard: selected ordered entries must already match the requested queue count, and selected unordered entries must be absent. The targeted path never repairs that global KV. A change before CAS prevents broker writes; a change afterward retains applied Broker truth and reports partial reconciliation failure.

Build the independent service​

From the repository root, enter the Control project. The first command builds the default no-mutation surface; the second selects the production tool capability:

cd rocketmq-ai/rocketmq-mcp-control
cargo build --locked --release
cargo build --locked --release --features write-tools

Choose the build needed rather than treating both as deployment steps. The executable is target/release/rocketmq-mcp-control, with .exe on Windows, unless Cargo's target directory is overridden. Root workspace builds do not include this project. The package selects Rust 2021 and MSRV 1.95.0.

Prepare configuration and real identity infrastructure​

Start from conf/mcp-control.example.toml. It is a template with example hosts and missing certificate/credential material, not a self-contained local environment.

SectionRequired setup
serverExplicit non-wildcard bind; example 127.0.0.1:8090 and /mcp. Use a canonical public HTTPS base URL and a real certificate/key pair.
oauthExact HTTPS issuer, audience and public HTTPS JWKS URL. Only RS256 OAuth JWTs are accepted, including bounded kid, signature, expiry, subject and rocketmq:write scope.
clustersMap a closed logical alias to its private NameServer endpoint and TLS policy; use environment-variable references for optional access/secret/security-token credentials. Inline secrets are rejected. For Proxy drains, list each Proxy under [[clusters.proxies]] with an alias (name) and its remoting host:port (endpoint).
mutationsInitially mutations_enabled=false, dry_run=true and empty operation/cluster allowlists. Enable only the intended operation and logical cluster when prerequisites are ready.
auditWritable durable JSONL destination; path is the active segment. The example keeps 4096 records per segment (capacity) and a maximum record size of 4096 bytes, the smallest value accepted. A full segment is sealed beside the active file as <path>.<six-digit number> and the server never deletes it, so archive sealed segments yourself. Preserve and recover the existing trail rather than replacing it on restart.
limitsAdmission control: per OAuth subject dry_runs_per_minute (60) and executes_per_minute (20), plus max_concurrent_calls (8) for the whole server. audit.capacity must be at least four times max_concurrent_calls.

Configuration rejects unknown fields and loads only at startup. TLS/audit path strings are used as configured; the loader does not rebase them to the TOML directory. Use absolute paths or keep a deliberate process working directory. ROCKETMQ_MCP_CONTROL_CONFIG selects the file; this binary has no --config CLI route.

The server and oauth rows state the defaults. A deployment inside a private network replaces them one by one with three switches, each off unless set to true: server.allow_wildcard_bind accepts a bind on every interface, such as 0.0.0.0:8443; server.allow_internal_base_url accepts a public_base_url with a port or with a host name that only resolves inside the network, such as https://control.mq.svc.cluster.local:8443; oauth.allow_private_network accepts an issuer and a JWKS URL with a port or an internal host name, and a JWKS endpoint at a private address. The server logs a warning at startup that names every switch that is on. With all of them on, the origin and both OAuth URLs are still canonical HTTPS URLs, a host is never an address literal or a loopback name, and a request must still name the configured origin in Host. The operations runbook states what the operator takes on with each one.

Without oauth.allow_private_network, JWKS retrieval rejects private, loopback, link-local or reserved DNS answers and rechecks addresses at connection time. A local fake issuer is therefore not a substitute for the production authentication path. Keys have a bounded five-minute generation lifetime with refresh/negative-cache controls. A JWKS entry is used when it is an RSA key of 2048 to 8192 bits with exponent 65537 whose use is absent or sig, whose alg is absent or RS256, and whose kid consists of ASCII letters, digits or ._:-; other entries, such as encryption keys, and members such as x5c are ignored. The server does not start when no entry is usable. Static tokens, HS algorithms and development authentication are unavailable. The HTTPS listener bounds requests to 1 MiB and 30 s; the example mutation operation timeout is 24 s.

A Proxy answers drain requests only for an authenticated principal, and a Broker applies a remote log filter only with authentication and authorization enabled and with [logging.reload] enabled = true. Give the cluster entry credentials that those targets accept before allowlisting the four operation-bound operations.

The identity provider must issue matching rocketmq_operations and rocketmq_clusters claims plus rocketmq:write. conf/permissions.example.toml describes that vocabulary; it is not a local permission file that bypasses OAuth. The subject must satisfy the safe bounded operator grammar documented in the tool/runbook sources.

After provisioning a configuration file, from the Control directory:

$env:ROCKETMQ_MCP_CONTROL_CONFIG = (Resolve-Path 'conf/mcp-control.local.toml').Path
cargo run --locked --release --features write-tools

For the first discovery, keep mutations disabled. To discover a selected tool, set mutations_enabled=true, add its closed operation and cluster allowlists, ensure a matching registry entry and OAuth claims, then restart. Even a dry-run requires those tool-enablement conditions. The fixed resource rocketmq-control://capabilities reports compile/runtime/registered state; mutation_supported is not true merely because the binary was built with a feature. Prompts and resource templates are empty.

Perform a dry-run before an intentional change​

Use an authenticated, initialized HTTPS MCP session. The following complete call requests a plan for an example logical cluster and Broker; replace those names with configured and authorized targets. It can read target state and write audit records, but dry_run=true does not apply the Broker patch:

{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "rocketmq_patch_broker_config",
"arguments": {
"schema_version": "rocketmq-mcp-control.arguments.v1",
"cluster": "production-a",
"broker_name": "broker-a",
"properties": { "traceTopicEnable": "true" },
"dry_run": true,
"confirm": false
}
}
}

Inspect aggregate before and requested plus broker-sorted target evidence. For an intentional execution, preserve the reviewed target/payload, explicitly set dry_run=false and confirm=true, and provide a safe reason such as CHG-10016 enable tracing. A reason is trimmed 5–256 byte ASCII using letters, digits, spaces and ._,#-; token-, address- and endpoint-shaped content is rejected. Dry-run can omit the reason. confirm defaults to false.

An optional request_key on an execute call provides process-local singleflight/result reuse for 10 minutes, bounded to 4096 entries. It is scoped by principal, operation, cluster, sorted targets and canonical payload. Only a settled outcome is kept: applied, partial or conflict. After a failure, a timeout or a cancellation, the same key runs preflight again instead of repeating the error, and a dry-run is never keyed. Reusing a key with a different payload is rejected. Cache hits/followers open no new Admin session, but each invocation still persists its own audit pair. This is not cross-restart exactly-once execution; a caller timeout is not a rollback guarantee.

A dry run reports plan_digest, a SHA-256 of what was asked, the state that was read, and what each target would get. Pass it back as expected_plan_digest when you execute: the server then writes only if the plan it reads is still that one, and otherwise ends the call with precondition_conflict without writing. Run the dry run again and execute the plan it reports now, under a new request_key. The argument is optional unless the server sets mutations.require_plan_digest = true; such a server refuses an execution without it as invalid_argument. The offsets of a consuming group move between two reads, so stop its consumers before planning an offset reset that you execute with a digest. The digest is a comparison, not an approval: the server stores none and records no reviewer.

Interpret results and recover​

Results use rocketmq-mcp-mutation.v1 with aggregate before, requested and after, plus per-target persistence and verification evidence. Read the result of a change from this response: Query MCP answers from caches that are kept for a few seconds, so a read through it right after an execution can still return the earlier state. The description of every mutation tool says so.

StatusInterpretation
plannedDry-run plan; no applied mutation
appliedApplied or unchanged success; changed=false is the no-change case
conflictprecondition_conflict; expected state changed and no automatic conflict retry occurs
partialpartial_apply; inspect each target rather than treating the operation as atomic
failedMay still include an applied target with failed persistence/post-read verification; inspect error_code and target evidence

Every result also carries invocation_id, which identifies the call in the audit log and in the service logs. When a response is lost or a call times out, rocketmq_get_mutation_result reads how the call ended, selected by that identifier or by the request_key that was sent: finished with its status, in_progress, interrupted when the server restarted before the outcome was recorded, or not_found. It is read-only, answers only for the caller's own calls on an authorized cluster, needs rocketmq:write but no operation claim, writes no audit record, and stays available after mutations are disabled. Send a request_key with every execute call you may need to look up, because an error envelope carries no identifier.

Conflict, partial and failed outcomes set MCP isError=true while retaining structured data. A failed result does not universally mean no effect. order_reconciliation_failed preserves Broker-applied state instead of rewriting the global order KV.

Two rejections happen before anything is written. An invalid_argument error can carry violations: up to three entries, each a JSON Pointer path into the arguments and a constraint such as required, maximum or additional_property, never a value that was sent. A call beyond the subject's per-minute budget or the server's concurrency cap gets rate_limited before its started record, so it writes no audit record and opens no session; retry it later.

Reliable audit failures normalize to audit_unavailable. Failed started persistence prevents sessions and RPC; terminal audit failure can occur after an effect and after bounded session shutdown. A partial disk tail or poisoned live audit trail needs explicit repair/recovery handling. Do not blindly retry a write after an ambiguous terminal failure.

Only the durable audit trail retains the validated OAuth subject and safe reason as operator evidence. Audit v3 records also name the validated logical targets, a Proxy alias included, and hold SHA-256 digests of the requested state, the request key and the pre-change state, whether a target was written, and per-target result counts; they never hold the requested values. Responses, ordinary logs, tracing and errors exclude operator and reason; all surfaces exclude credentials, tokens, endpoints, message bodies and raw backend errors. See the operations runbook for audit recovery and partial-target investigation.

To stop new mutations at once, create the file that mutations.emergency_stop_file names: while it exists, every mutation call is refused with mutation_disabled, without a restart, and removing it lifts the stop. To make a stop last, disable mutations_enabled or remove the operation/cluster allowlist and restart; the rest of the configuration does not hot reload. Reconcile uncertain target state through separately authorized operational tools. The Control server does not invent compensating writes. These are product runtime controls; omitting documentation approval gates does not remove them.

This page verifies source-defined configuration, catalog and schemas. No OAuth deployment, real Control session or cluster mutation was executed during documentation work.

Sources: manifest, configuration loader, process entry, catalog and product boundary.