@alexbuzo/dzengi-mcp
v0.1.1
Published
Safe Dzengi MCP server for read-only and explicitly guarded trading workflows
Maintainers
Readme
@alexbuzo/dzengi-mcp
Safe, local Model Context Protocol (MCP) access to Dzengi market, account, order, and position data. Trading tools are separate, explicitly guarded mutations; the server is read-only until its policy gates are enabled.
Financial-risk warning: trading digital assets and leveraged products can lose money quickly, including more than the amount initially committed. This package is infrastructure, not investment advice or a trading strategy. Review every order, account, symbol, quantity, price, leverage, and stop value yourself. Start with demo and read-only API keys. Never give an agent more permission than you can afford to use.
1. Scope and safety boundary
The package runs a Node.js 20+ stdio MCP server against the official Dzengi
REST adapter. It exposes curated typed tools, not an arbitrary HTTP proxy.
Public market reads work without credentials; signed account reads and every
mutation require credentials at call time. dzengi_list_instruments reads the
public exchangeInfo catalog without credentials, but automatically uses the
account-scoped catalog when both DZENGI_API_KEY and DZENGI_API_SECRET are
configured. Supplying only one credential keeps this read fully public and
never sends a partial credential pair.
The server does not implement withdrawals, deposits, transfers, funding, or
account-management operations. It cannot move funds. An account response may
contain broker metadata such as canWithdraw or canDeposit, but those flags
do not add a funding tool to this server. It also does not run a trading
strategy, store credentials remotely, or maintain WebSocket subscriptions.
Stdout is reserved for MCP protocol frames. Startup and audit diagnostics go to stderr, and secrets, signatures, authorization headers, and complete signed URLs are redacted. Successful tool results are recursively sanitized and bounded to 1 MiB of UTF-8 JSON; an oversized result is returned as a safe validation error instead of a partial response.
Broker HTTP response bodies are separately bounded to 2 MiB of decompressed bytes before JSON parsing. An oversized or malformed read is returned as a safe HTTP error; a dispatched mutation is reported as having an unknown outcome and is never retried.
The transport rejects redirects (redirect: "error") for every broker
request, so an API key or signed query cannot be forwarded to another origin.
A read redirect is a safe HTTP failure; a redirect rejection after mutation
dispatch is an unknown outcome that requires reconciliation. Swagger's cancel
endpoints may return 204 No Content, which is accepted as an empty success
result; other empty 2xx responses remain malformed.
2. Install from npm or source
The npm package name is @alexbuzo/dzengi-mcp and its executable is
dzengi-mcp. Run the package from an environment that supplies configuration
through environment variables:
npx -y @alexbuzo/[email protected]Run this published-package command outside the source checkout. Inside a
checkout with the same package name and version, npm can select the local
package without installing its executable, producing dzengi-mcp: command
not found. Set the MCP launcher's working directory to a neutral directory,
or use the source-checkout commands below. After changing source code, rebuild
and launch node /absolute/path/to/dzengi-mcp/dist/index.js to use those changes;
an npx command pinned to a published version still runs that published version.
For a source checkout:
git clone https://github.com/abuzo/DzengiMcp.git dzengi-mcp
cd dzengi-mcp
npm ci
cp .env.example .env
npm run build
npm start.env is for local development only and is ignored by Git. Do not commit it,
paste credentials into this README, or put secrets in a Codex TOML file.
The default environment is the official demo adapter at
https://demo-api-adapter.dzengi.com with API v1. Live uses
https://api-adapter.dzengi.com; demo API v2 is rejected by configuration.
3. Create a restricted Dzengi API key
Follow Dzengi's API Get Started guide: sign in, open Settings > API integrations > Generate new key, enable 2FA, set permissions, bind an IP address, and set an expiration date.
Use separate keys for demo and live. Begin with the smallest read-only permissions needed for market/account inspection. Add trade permission only after the demo workflow is understood. Disable withdrawal, deposit, transfer, or other funding permissions if the Dzengi account UI offers them; this server does not need them. Bind the key to the narrowest stable egress IP, enable 2FA on the account, set a short expiration, and record the expiry owner and date. Keep the API secret in an environment manager or OS keychain, never in source, shell history, logs, screenshots, tool arguments, or a Codex configuration value. Rotate and revoke keys on the schedule in section 10.
4. Start with demo, read-only
The safe baseline is:
DZENGI_ENV=demo
DZENGI_API_VERSION=1
DZENGI_ALLOW_TRADE=false
DZENGI_ALLOW_LIVE_TRADING=false
DZENGI_REQUIRE_CONFIRMATION=true
DZENGI_MAX_LEVERAGE=1Credentials are not needed for public reads. After building from source, check the executable and protocol boundary without contacting a trading endpoint:
npm run build
npm run verify:stdioUse dzengi_get_runtime_status, dzengi_get_server_time,
dzengi_list_instruments, and dzengi_get_ticker first. Signed account reads
will return AUTH_REQUIRED until both credential variables are supplied. With
the baseline gates, all six mutation tools remain denied even if a client asks
for confirm: true.
5. Configure Codex without embedding values
Codex forwards the names below from the environment in which it starts the server. The TOML contains names, not API keys or secrets:
[mcp_servers.dzengi]
command = "npx"
args = ["-y", "@alexbuzo/[email protected]"]
env_vars = [
"DZENGI_ENV",
"DZENGI_API_KEY",
"DZENGI_API_SECRET",
"DZENGI_ALLOW_TRADE",
"DZENGI_ALLOW_LIVE_TRADING",
"DZENGI_MAX_ORDER_NOTIONAL",
"DZENGI_MAX_LEVERAGE",
"DZENGI_ALLOWED_SYMBOLS"
]
default_tools_approval_mode = "writes"This follows the Codex MCP configuration guide. Set the forwarded values in the local process environment or your approved secret manager. Codex approvals are a client-side safety layer; the server's trade flags, confirmation requirement, allowlist, notional limit, leverage limit, and live dual gate remain authoritative. A stricter per-tool approval policy is encouraged for new deployments.
Configuration reference
All names below are read by loadConfig; blank optional values are omitted.
| Variable | Default / accepted values | Purpose |
| --- | --- | --- |
| DZENGI_ENV | demo or live (default demo) | Selects the official adapter host. |
| DZENGI_API_VERSION | 1 or 2; defaults to 1 for demo and 2 for live | Demo v2 is rejected. |
| DZENGI_API_KEY | blank | Signed-request key; public reads do not need it. |
| DZENGI_API_SECRET | blank | HMAC secret; never returned in status or errors. |
| DZENGI_ALLOW_TRADE | false (strict true/false) | Master mutation gate. |
| DZENGI_ALLOW_LIVE_TRADING | false (strict true/false) | Required in addition to the master gate for live. |
| DZENGI_REQUIRE_CONFIRMATION | true (strict true/false) | Requires confirm: true on every mutation; live trading enforces true at startup. |
| DZENGI_MAX_ORDER_NOTIONAL | blank or positive plain decimal | Maximum order notional; mandatory when live trading is enabled. |
| DZENGI_MAX_LEVERAGE | 1, up to 1000 | Maximum requested leverage. |
| DZENGI_ALLOWED_SYMBOLS | blank or comma-separated symbols | Optional trimmed, case-sensitive allowlist; copy symbols exactly from dzengi_list_instruments, including case and punctuation. |
| DZENGI_RECV_WINDOW_MS | 5000, integer 1..60000 | Signed request timing window. |
| DZENGI_TIMEOUT_MS | 10000, integer 100..120000 | HTTP timeout. |
| DZENGI_READ_RETRIES | 2, integer 0..5 | Bounded retries for transient reads only. |
| DZENGI_BASE_URL | blank (derived from DZENGI_ENV) | Official host override; custom hosts require the next flag. Signed reads and mutations send the API key and HMAC signature to the configured host. |
| DZENGI_ALLOW_CUSTOM_BASE_URL | false (strict true/false) | Explicitly permits a non-official, fully trusted host. Custom non-loopback hosts must use HTTPS. |
| DZENGI_AUDIT_LOG_PATH | blank | Optional append-only JSONL mutation audit path; it must resolve to a regular file (stdout/stderr descriptor aliases and special files are rejected), and newly created files use mode 600. |
| DZENGI_LOG_LEVEL | info; debug, info, warn, or error | Secret-free stderr verbosity. |
DZENGI_LOG_LEVEL controls lifecycle information only: debug and info show
startup information, while warn and error suppress it. Startup and
shutdown errors always remain on stderr. Mutation audit events are independent
of this filter and continue to be emitted to stderr and the configured audit
file when enabled. Runtime status reports only whether audit logging is enabled;
it never returns the configured local audit path. Its baseUrl status is the
configured endpoint origin only; endpoint paths are not returned.
Only set DZENGI_BASE_URL to an endpoint you fully trust: signed reads and every
mutation send X-MBX-APIKEY and a query HMAC signature to that host. Keep the
default official host unless a trusted test or gateway endpoint is required.
Changing any environment value, especially either trade flag, takes effect only after the MCP process is restarted because configuration is loaded once.
6. Tool catalog
There are exactly 22 curated tools. Read tools have read-only MCP annotations;
mutations have destructive/write annotations and always require a caller-owned
clientRequestId plus a boolean confirm field.
Read-only market and runtime tools
| Tool | What it does |
| --- | --- |
| dzengi_get_runtime_status | Reports environment, API version, configured endpoint origin, gates, limits, credential-presence booleans, and the audit-enabled flag; it never exposes endpoint paths or the local audit path. |
| dzengi_get_server_time | Reads server time and refreshes the local clock offset. |
| dzengi_list_instruments | Reads account-scoped exchangeInfo when both credentials are configured, otherwise public exchangeInfo, with bounded local pagination (offset, limit). |
| dzengi_get_ticker | Reads an optional-symbol 24-hour ticker. |
| dzengi_get_order_book | Reads bounded depth for a required symbol and optional limit. |
| dzengi_get_candles | Reads bounded candles for symbol, interval, and optional time/price filters. |
| dzengi_get_trading_fees | Reads optional-symbol fee information. |
| dzengi_get_trading_limits | Reads optional-symbol broker limits. |
| dzengi_get_leverage_settings | Reads signed leverage settings for an exact symbol whose marketType is LEVERAGE. A rejected request for a known SPOT instrument returns a descriptive validation error; symbols are never converted. |
Read-only account and lifecycle tools
| Tool | What it does |
| --- | --- |
| dzengi_get_account | Reads signed account permissions and balances; optional showZeroBalance. |
| dzengi_list_open_orders | Reads open orders, optionally filtered by symbol. |
| dzengi_get_order | Reads a signed order by required symbol and orderId. |
| dzengi_list_positions | Reads current leverage positions. |
| dzengi_list_trades | Reads bounded signed trades for required symbol and optional time/limit filters. |
| dzengi_list_position_history | Reads bounded position history with optional symbol, from, to, and limit. |
| dzengi_preflight_order | Validates a proposed order against current metadata and policy without placing it. |
Guarded mutation tools
| Tool | Financial operation and additional fields |
| --- | --- |
| dzengi_place_order | Places a MARKET, LIMIT, or STOP order. Required: symbol, type, side, quantity; optional: price, accountId, leverage, expireTimestamp, newOrderRespType, stopLoss, takeProfit, stopDistance, profitDistance, trailingStopLoss, guaranteedStopLoss. |
| dzengi_cancel_order | Cancels by required symbol and orderId; it never assumes USD. |
| dzengi_edit_order | Edits an exchange order by required safety-only symbol and orderId; provide price and/or expireTimestamp. |
| dzengi_update_order | Updates a leverage order by required safety-only symbol and orderId; provide at least one of newPrice, expireTimestamp, stopLoss, takeProfit, stopDistance, profitDistance, trailingStopLoss, or guaranteedStopLoss. |
| dzengi_close_position | Closes a leverage position by required safety-only symbol and positionId. |
| dzengi_update_position | Updates position protection by required safety-only symbol and positionId; provide at least one stop/protection field. |
Every mutation is sent at most once by the transport. An ID must never be reused: while an entry remains in the bounded in-memory cache (up to 24 hours and 1,024 completed entries), an identical request replays and a different financial payload is rejected. Restart, TTL expiry, or capacity eviction removes that local protection, so callers must reconcile before any new request.
Lifecycle mutations use symbol for allowlist and signed ownership checks, then
omit it from the broker mutation payload. The signed order lookup is already
scoped by the exact {symbol, orderId} query, so a single matching order record
may omit symbol; explicit mismatches and ambiguous records still fail closed.
Position records must include the exact symbol. Order edits with a replacement
price also re-check the associated quantity against the configured notional cap.
Use the exact canonical symbol, including case and punctuation, returned by
dzengi_list_instruments for all symbol-bearing tools.
7. Enable demo mutations deliberately
Only do this with a demo account after the read-only checks pass. Replace every
REPLACE_WITH_... value; these are intentionally fake placeholders, not
credentials or live symbols:
export DZENGI_ENV=demo
export DZENGI_API_VERSION=1
export DZENGI_API_KEY=REPLACE_WITH_DEMO_API_KEY
export DZENGI_API_SECRET=REPLACE_WITH_DEMO_API_SECRET
export DZENGI_ALLOW_TRADE=true
export DZENGI_ALLOW_LIVE_TRADING=false
export DZENGI_REQUIRE_CONFIRMATION=true
export DZENGI_MAX_ORDER_NOTIONAL=10
export DZENGI_MAX_LEVERAGE=1
export DZENGI_ALLOWED_SYMBOLS=REPLACE_WITH_DEMO_SYMBOL
npm run build
npm startThe server still requires confirm: true on each mutation and a new
clientRequestId. Keep the notional and allowlist as small as practical. A
demo key should have no funding permission and should be bound and expired like
a live key.
8. Enable live mutations only with all gates
Live trading requires every item below at startup:
DZENGI_ENV=liveand a live API key/secret;DZENGI_ALLOW_TRADE=true;DZENGI_ALLOW_LIVE_TRADING=true;- a positive
DZENGI_MAX_ORDER_NOTIONAL; - a deliberate
DZENGI_MAX_LEVERAGEand, preferably, a narrowDZENGI_ALLOWED_SYMBOLSlist; DZENGI_REQUIRE_CONFIRMATION=true(enforced at startup) andconfirm: trueper mutation; and- Codex write approval enabled for the client session.
Example shape, with deliberately fake credential placeholders:
export DZENGI_ENV=live
export DZENGI_API_VERSION=2
export DZENGI_API_KEY=REPLACE_WITH_LIVE_API_KEY
export DZENGI_API_SECRET=REPLACE_WITH_LIVE_API_SECRET
export DZENGI_ALLOW_TRADE=true
export DZENGI_ALLOW_LIVE_TRADING=true
export DZENGI_REQUIRE_CONFIRMATION=true
export DZENGI_MAX_ORDER_NOTIONAL=10
export DZENGI_MAX_LEVERAGE=1
export DZENGI_ALLOWED_SYMBOLS=REPLACE_WITH_LIVE_SYMBOL
npm run build
npm startThe example limit is not a recommendation; choose a limit appropriate to the
account and risk policy. Configuration rejects live trading without
DZENGI_MAX_ORDER_NOTIONAL. Restart after every gate or credential change,
then inspect dzengi_get_runtime_status before considering a write.
DZENGI_MAX_ORDER_NOTIONAL is measured in quote currency as quantity × limit or
stop price (or the current market reference price for a market order). It is a
pre-dispatch cap, not a guarantee against execution price or slippage.
9. Preflight, place, and reconcile
Dzengi's native exchangeInfo reports the price increment in top-level
tickSize and may omit minPrice/maxPrice entirely. Preflight checks this
increment without treating absent optional price bounds as an API error.
Declared but incomplete price filters and malformed explicit bounds still
produce warnings. Quantity filters, broker minimum notional, configured caps,
and confirmation requirements continue to apply independently.
Use an obviously fake symbol in documentation examples and replace it only
with a symbol returned by dzengi_list_instruments:
{
"tool": "dzengi_preflight_order",
"arguments": {
"symbol": "REPLACE_WITH_DEMO_SYMBOL",
"type": "LIMIT",
"side": "BUY",
"quantity": "0.01",
"price": "1.00",
"confirm": true
}
}Placement must consume a fresh report with allowed: true. The mutation adds
the explicit confirmation and a new request ID; it is not a shell command and
must be issued through the MCP client:
{
"tool": "dzengi_place_order",
"arguments": {
"clientRequestId": "REPLACE_WITH_NEW_UUID",
"confirm": true,
"symbol": "REPLACE_WITH_DEMO_SYMBOL",
"type": "LIMIT",
"side": "BUY",
"quantity": "0.01",
"price": "1.00"
}
}An order mutation can cross the broker boundary before a timeout, network
failure, malformed response, or HTTP 5xx is observed. The server then returns
MUTATION_OUTCOME_UNKNOWN with reconciliation guidance. Do not retry the
mutation. Inspect, in order as applicable:
dzengi_list_open_ordersfor the symbol;dzengi_get_orderwith the knownsymbolandorderId;dzengi_list_tradesfor fills; anddzengi_list_positionsfor position state.
On the same running process, repeating the identical clientRequestId returns
the cached unknown result without dispatching again. A different financial
payload under that ID is rejected. The local guard is memory-only: it does not
survive a process restart and is not a broker idempotency guarantee. Never
reuse a mutation ID after restart; reconcile broker state first and choose a
new ID only for a deliberately new action.
10. Emergency disablement and credential rotation
The emergency kill switch is to set both trade gates false and restart the process:
export DZENGI_ALLOW_TRADE=false
export DZENGI_ALLOW_LIVE_TRADING=false
npm startStop the existing process first (Ctrl-C for a foreground process). Changing
the variables without restarting does not change the active policy. Verify
dzengi_get_runtime_status shows both gates disabled. If a key may have been
exposed, revoke it in Dzengi immediately; do not rely on the process restart
alone.
For rotation, generate a new key with the same least-privilege permissions, IP binding, 2FA, and expiry policy; update the external secret store or local environment; restart; run a public read and (if appropriate) a signed read; then revoke the old key. Never print either value while checking the change.
Audit file operations
DZENGI_AUDIT_LOG_PATH is optional. When set, the server appends one
secret-free JSONL start/terminal pair per mutation attempt; it does not persist
ordinary read responses or unrestricted broker payloads. Treat the file as
sensitive operational data even though credentials and signatures are redacted.
Create an owner-only directory and file before starting the server:
umask 077
install -d -m 700 /var/lib/dzengi-mcp/audit
touch /var/lib/dzengi-mcp/audit/mutations.jsonl
chmod 600 /var/lib/dzengi-mcp/audit/mutations.jsonl
export DZENGI_AUDIT_LOG_PATH=/var/lib/dzengi-mcp/audit/mutations.jsonlThe writer is append-only at the application level. The configured path is
read once at startup, while each event opens the configured path for an append;
the process does not automatically follow a renamed rotation target. To rotate
safely, stop the server (and let any in-flight write finish), move the old file
to an owner-only archive, create a new 600 file, update
DZENGI_AUDIT_LOG_PATH, and restart. Verify the new process's runtime status
and that a deliberately chosen test mutation/readiness check writes to the new
path; never use a live trade as a logging test. Set an operator-owned retention
period, protect backups, and use the organization's approved secure-deletion
procedure when records expire. Audit files are local state and must never be
committed, included in an npm tarball, or shipped to a support ticket without
redaction.
Rollback to a known-good release
Select a previously verified package version and pin it instead of relying on
latest. For example, the Codex entry can temporarily use an owner-approved
version tag:
[mcp_servers.dzengi]
command = "npx"
args = ["-y", "@alexbuzo/[email protected]"]Stop the existing MCP process or supervisor unit, restore the known-good
environment file and safety gates (start with both trade gates false), and
restart the pinned version. Verify dzengi_get_runtime_status, then a public
read such as dzengi_get_server_time; perform dzengi_get_account only when a
signed read is appropriate and credentials have been checked independently.
Do not use a mutation to validate a rollback. If compromise is possible,
revoke the affected key, issue a replacement with the same least-privilege/IP/
2FA/expiry policy, update the secret store, and restart again. npm publication
and version promotion remain manual owner actions.
11. Errors and unknown outcomes
MCP failures are structured and set isError: true; the text and structured
representations contain the same safe error projection. The stable codes are:
| Code | Meaning |
| --- | --- |
| CONFIG_ERROR | Invalid environment, unsupported API version, unsafe host, or missing live limit. |
| VALIDATION_ERROR | Tool input, precision, required-field, account-selection, or preflight validation failed. |
| POLICY_DENIED | Trade gate, live gate, confirmation, symbol allowlist, notional, or leverage policy denied the mutation. |
| AUTH_REQUIRED | A signed read or mutation was requested without both credentials. |
| RATE_LIMITED | Broker or local pacing rejected the request; read retries remain bounded. |
| DZENGI_HTTP_ERROR | A non-success HTTP response or transport failure was classified as an HTTP error. |
| DZENGI_API_ERROR | Dzengi returned a non-success API envelope or malformed success payload. |
| MUTATION_OUTCOME_UNKNOWN | A dispatched mutation may have succeeded; reconcile before any new action. |
Only idempotent reads retry bounded transient statuses (408, 429, 500,
502, 503, 504). Writes are never automatically retried, including after
timestamp, timeout, network, malformed-response, or 5xx failures. The shared
limiter stays below Dzengi's documented 10 requests/second limit and applies a
separate margin for openOrders.
12. Development, tests, and owner release commands
Requirements: Node.js 20 or newer and npm. The offline development gate is:
npm ci
npm test
npm run typecheck
npm run lint
npm run build
npm run verify:stdio
npm run verify:packnpm run verify runs the test, type, lint, build, stdio, and package gates in
one command. npm run dev runs the TypeScript entry point for local work;
npm run clean removes dist. npm run update:openapi refreshes the checked-
in official Swagger snapshot and npm run check:openapi checks for drift; both
are maintainer commands that need network access and their source snapshots are
not shipped in the npm payload. npm run format formats the repository.
Before an owner release, inspect the dry-run package and then use the existing owner npm commands:
npm pack --dry-run
npm publish --access publicThis repository's Task 11 verification does not publish. The package's
prepack build and prepublishOnly test/type/lint/build hooks provide an
additional release guard; npm run verify:pack invokes dry-run packing with
--ignore-scripts so the verifier cannot recursively invoke those hooks.
13. Official documentation
- Dzengi API landing page
- Dzengi Swagger UI
- Dzengi public Swagger JSON
- Dzengi General REST API Information
- Dzengi API Get Started
- Dzengi API Changelog
- OpenAI Codex MCP configuration
When broker behavior and this README differ, verify the official Dzengi documentation and the checked-in Swagger snapshot before changing a limit or endpoint. The package intentionally favors a fail-closed result over guessing.
