{"_id":"@alexbuzo/dzengi-mcp","_rev":"2-0da95426a72c6a241fc38b2a909df468","name":"@alexbuzo/dzengi-mcp","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@alexbuzo/dzengi-mcp","version":"0.1.0","keywords":["dzengi","mcp","model-context-protocol","trading","rest-api"],"license":"MIT","_id":"@alexbuzo/dzengi-mcp@0.1.0","maintainers":[{"name":"alexbuzo","email":"aliakseibuzo@gmail.com"}],"homepage":"https://github.com/abuzo/DzengiMcp#readme","bugs":{"url":"https://github.com/abuzo/DzengiMcp/issues"},"bin":{"dzengi-mcp":"dist/index.js"},"dist":{"shasum":"abdf1cbdfd80d56638faf357c1e70c8f781ea642","tarball":"https://registry.npmjs.org/@alexbuzo/dzengi-mcp/-/dzengi-mcp-0.1.0.tgz","fileCount":75,"integrity":"sha512-0HuOHtmHxFqI0fHMwS8N6LAtv56YqytGZcGG4mgkyQHDmSVBWOA7aJG9ds+Q7TgpObiDK9Zt0b0t7HaCMXxl6w==","signatures":[{"sig":"MEYCIQCg6D7HQRhuJmxEmrwR5og0zMXNq2tnNpLgDado/KqD1QIhAOyXjBavKWVzlslUC8Alhb0xL3Mh+/Q5+5/9bJDimEzJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":453468},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":">=20"},"exports":{".":"./dist/index.js"},"gitHead":"4947d2e29fcaf344ed6c459d27ecfa61fbcd41ae","scripts":{"dev":"tsx src/index.ts","lint":"eslint .","test":"vitest run","build":"npm run clean && tsc","clean":"node -e \"fs.rmSync('dist', { recursive: true, force: true })\"","start":"node dist/index.js","format":"prettier --write .","verify":"npm test && npm run typecheck && npm run lint && npm run build && npm run verify:stdio && npm run verify:pack","prepack":"npm run build","typecheck":"tsc --noEmit","test:watch":"vitest","verify:pack":"node scripts/verify-pack.mjs","verify:stdio":"node scripts/smoke-stdio.mjs","check:openapi":"node scripts/update-openapi.mjs --check","prepublishOnly":"npm test && npm run typecheck && npm run lint && npm run build","update:openapi":"node scripts/update-openapi.mjs"},"_npmUser":{"name":"alexbuzo","email":"aliakseibuzo@gmail.com"},"repository":{"url":"git+https://github.com/abuzo/DzengiMcp.git","type":"git"},"_npmVersion":"11.12.1","description":"Safe Dzengi MCP server for read-only and explicitly guarded trading workflows","directories":{},"_nodeVersion":"25.9.0","dependencies":{"zod":"^3.25.76","dotenv":"^17.2.3","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.6","eslint":"^9.39.1","vitest":"^4.0.3","prettier":"^3.6.2","@eslint/js":"^9.39.1","typescript":"^5.9.3","@types/node":"^22.10.2","typescript-eslint":"^8.46.3"},"_npmOperationalInternal":{"tmp":"tmp/dzengi-mcp_0.1.0_1788993592031_0.4376020713299875","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"_id":"@alexbuzo/dzengi-mcp@0.1.1","bin":{"dzengi-mcp":"dist/index.js"},"bugs":{"url":"https://github.com/abuzo/DzengiMcp/issues"},"dist":{"shasum":"fdc5eb4c21e828464867ed0477a137af8ee35031","tarball":"https://registry.npmjs.org/@alexbuzo/dzengi-mcp/-/dzengi-mcp-0.1.1.tgz","fileCount":75,"integrity":"sha512-yjJ5wCUzFpce86VhMcDdS4S3boGG9MUnvIucR6t+um1cyA7m0ptLubmwPNhB2xqlPudNtOuHkwN7gR57GTd1xA==","signatures":[{"sig":"MEUCIE0AHh9fIYvVDPHsTfpS04D0mNkyGmDe0mNYMCvIYC2xAiEAui1dvVEpBVavTJqx9SNdxpfn7N4tg1Ng+gh/bM1MRgE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDPEeZfDR31XOXZqLgyzAPPLYujibiKwNlRgFSQ/EOsQgIgNfOuLITrXsQrryN7OZ+yBZ+JF+++2uqwUU7b/0435WY="}],"unpackedSize":458633},"main":"dist/index.js","name":"@alexbuzo/dzengi-mcp","type":"module","types":"dist/index.d.ts","module":"dist/index.js","engines":{"node":">=20"},"exports":{".":"./dist/index.js"},"gitHead":"a63823153d610f112b0c935063e8ee5601e9f84b","license":"MIT","scripts":{"dev":"tsx src/index.ts","lint":"eslint .","test":"vitest run","build":"npm run clean && tsc","clean":"node -e \"fs.rmSync('dist', { recursive: true, force: true })\"","start":"node dist/index.js","format":"prettier --write .","verify":"npm test && npm run typecheck && npm run lint && npm run build && npm run verify:stdio && npm run verify:pack","prepack":"npm run build","typecheck":"tsc --noEmit","test:watch":"vitest","verify:pack":"node scripts/verify-pack.mjs","verify:stdio":"node scripts/smoke-stdio.mjs","check:openapi":"node scripts/update-openapi.mjs --check","prepublishOnly":"npm test && npm run typecheck && npm run lint && npm run build","update:openapi":"node scripts/update-openapi.mjs"},"version":"0.1.1","_npmUser":{"name":"alexbuzo","email":"aliakseibuzo@gmail.com"},"homepage":"https://github.com/abuzo/DzengiMcp#readme","keywords":["dzengi","mcp","model-context-protocol","trading","rest-api"],"repository":{"url":"git+https://github.com/abuzo/DzengiMcp.git","type":"git"},"_npmVersion":"11.12.1","description":"Safe Dzengi MCP server for read-only and explicitly guarded trading workflows","directories":{},"maintainers":[{"name":"alexbuzo","email":"aliakseibuzo@gmail.com"}],"_nodeVersion":"25.9.0","dependencies":{"zod":"^3.25.76","dotenv":"^17.2.3","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.6","eslint":"^9.39.1","vitest":"^4.0.3","prettier":"^3.6.2","@eslint/js":"^9.39.1","typescript":"^5.9.3","@types/node":"^22.10.2","typescript-eslint":"^8.46.3"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dzengi-mcp_0.1.1_1789046947695_0.6993646962278086"}}},"time":{"created":"2026-09-09T22:39:51.834Z","modified":"2026-09-10T13:29:07.962Z","0.1.0":"2026-09-09T22:39:52.212Z","0.1.1":"2026-09-10T13:29:07.793Z"},"bugs":{"url":"https://github.com/abuzo/DzengiMcp/issues"},"license":"MIT","homepage":"https://github.com/abuzo/DzengiMcp#readme","keywords":["dzengi","mcp","model-context-protocol","trading","rest-api"],"repository":{"url":"git+https://github.com/abuzo/DzengiMcp.git","type":"git"},"description":"Safe Dzengi MCP server for read-only and explicitly guarded trading workflows","maintainers":[{"name":"alexbuzo","email":"aliakseibuzo@gmail.com"}],"readme":"# @alexbuzo/dzengi-mcp\n\nSafe, local Model Context Protocol (MCP) access to Dzengi market, account,\norder, and position data. Trading tools are separate, explicitly guarded\nmutations; the server is read-only until its policy gates are enabled.\n\n> **Financial-risk warning:** trading digital assets and leveraged products can\n> lose money quickly, including more than the amount initially committed.\n> This package is infrastructure, not investment advice or a trading strategy.\n> Review every order, account, symbol, quantity, price, leverage, and stop\n> value yourself. Start with demo and read-only API keys. Never give an agent\n> more permission than you can afford to use.\n\n## 1. Scope and safety boundary\n\nThe package runs a Node.js 20+ stdio MCP server against the official Dzengi\nREST adapter. It exposes curated typed tools, not an arbitrary HTTP proxy.\nPublic market reads work without credentials; signed account reads and every\nmutation require credentials at call time. `dzengi_list_instruments` reads the\npublic `exchangeInfo` catalog without credentials, but automatically uses the\naccount-scoped catalog when both `DZENGI_API_KEY` and `DZENGI_API_SECRET` are\nconfigured. Supplying only one credential keeps this read fully public and\nnever sends a partial credential pair.\n\nThe server does not implement withdrawals, deposits, transfers, funding, or\naccount-management operations. It cannot move funds. An account response may\ncontain broker metadata such as `canWithdraw` or `canDeposit`, but those flags\ndo not add a funding tool to this server. It also does not run a trading\nstrategy, store credentials remotely, or maintain WebSocket subscriptions.\n\nStdout is reserved for MCP protocol frames. Startup and audit diagnostics go\nto stderr, and secrets, signatures, authorization headers, and complete signed\nURLs are redacted. Successful tool results are recursively sanitized and\nbounded to 1 MiB of UTF-8 JSON; an oversized result is returned as a safe\nvalidation error instead of a partial response.\n\nBroker HTTP response bodies are separately bounded to 2 MiB of decompressed\nbytes before JSON parsing. An oversized or malformed read is returned as a\nsafe HTTP error; a dispatched mutation is reported as having an unknown\noutcome and is never retried.\n\nThe transport rejects redirects (`redirect: \"error\"`) for every broker\nrequest, so an API key or signed query cannot be forwarded to another origin.\nA read redirect is a safe HTTP failure; a redirect rejection after mutation\ndispatch is an unknown outcome that requires reconciliation. Swagger's cancel\nendpoints may return `204 No Content`, which is accepted as an empty success\nresult; other empty `2xx` responses remain malformed.\n\n## 2. Install from npm or source\n\nThe npm package name is `@alexbuzo/dzengi-mcp` and its executable is\n`dzengi-mcp`. Run the package from an environment that supplies configuration\nthrough environment variables:\n\n```bash\nnpx -y @alexbuzo/dzengi-mcp@0.1.0\n```\n\nRun this published-package command outside the source checkout. Inside a\ncheckout with the same package name and version, npm can select the local\npackage without installing its executable, producing `dzengi-mcp: command\nnot found`. Set the MCP launcher's working directory to a neutral directory,\nor use the source-checkout commands below. After changing source code, rebuild\nand launch `node /absolute/path/to/dzengi-mcp/dist/index.js` to use those changes;\nan `npx` command pinned to a published version still runs that published version.\n\nFor a source checkout:\n\n```bash\ngit clone https://github.com/abuzo/DzengiMcp.git dzengi-mcp\ncd dzengi-mcp\nnpm ci\ncp .env.example .env\nnpm run build\nnpm start\n```\n\n`.env` is for local development only and is ignored by Git. Do not commit it,\npaste credentials into this README, or put secrets in a Codex TOML file.\n\nThe default environment is the official demo adapter at\n`https://demo-api-adapter.dzengi.com` with API v1. Live uses\n`https://api-adapter.dzengi.com`; demo API v2 is rejected by configuration.\n\n## 3. Create a restricted Dzengi API key\n\nFollow Dzengi's [API Get Started guide](https://dzengi.com/api-get-started):\nsign in, open **Settings > API integrations > Generate new key**, enable 2FA,\nset permissions, bind an IP address, and set an expiration date.\n\nUse separate keys for demo and live. Begin with the smallest read-only\npermissions needed for market/account inspection. Add trade permission only\nafter the demo workflow is understood. Disable withdrawal, deposit, transfer,\nor other funding permissions if the Dzengi account UI offers them; this server\ndoes not need them. Bind the key to the narrowest stable egress IP, enable 2FA\non the account, set a short expiration, and record the expiry owner and date.\nKeep the API secret in an environment manager or OS keychain, never in source,\nshell history, logs, screenshots, tool arguments, or a Codex configuration\nvalue. Rotate and revoke keys on the schedule in section 10.\n\n## 4. Start with demo, read-only\n\nThe safe baseline is:\n\n```dotenv\nDZENGI_ENV=demo\nDZENGI_API_VERSION=1\nDZENGI_ALLOW_TRADE=false\nDZENGI_ALLOW_LIVE_TRADING=false\nDZENGI_REQUIRE_CONFIRMATION=true\nDZENGI_MAX_LEVERAGE=1\n```\n\nCredentials are not needed for public reads. After building from source, check\nthe executable and protocol boundary without contacting a trading endpoint:\n\n```bash\nnpm run build\nnpm run verify:stdio\n```\n\nUse `dzengi_get_runtime_status`, `dzengi_get_server_time`,\n`dzengi_list_instruments`, and `dzengi_get_ticker` first. Signed account reads\nwill return `AUTH_REQUIRED` until both credential variables are supplied. With\nthe baseline gates, all six mutation tools remain denied even if a client asks\nfor `confirm: true`.\n\n## 5. Configure Codex without embedding values\n\nCodex forwards the names below from the environment in which it starts the\nserver. The TOML contains names, not API keys or secrets:\n\n```toml\n[mcp_servers.dzengi]\ncommand = \"npx\"\nargs = [\"-y\", \"@alexbuzo/dzengi-mcp@0.1.0\"]\nenv_vars = [\n  \"DZENGI_ENV\",\n  \"DZENGI_API_KEY\",\n  \"DZENGI_API_SECRET\",\n  \"DZENGI_ALLOW_TRADE\",\n  \"DZENGI_ALLOW_LIVE_TRADING\",\n  \"DZENGI_MAX_ORDER_NOTIONAL\",\n  \"DZENGI_MAX_LEVERAGE\",\n  \"DZENGI_ALLOWED_SYMBOLS\"\n]\ndefault_tools_approval_mode = \"writes\"\n```\n\nThis follows the [Codex MCP configuration guide](https://developers.openai.com/codex/mcp).\nSet the forwarded values in the local process environment or your approved\nsecret manager. Codex approvals are a client-side safety layer; the server's\ntrade flags, confirmation requirement, allowlist, notional limit, leverage\nlimit, and live dual gate remain authoritative. A stricter per-tool approval\npolicy is encouraged for new deployments.\n\n### Configuration reference\n\nAll names below are read by `loadConfig`; blank optional values are omitted.\n\n| Variable | Default / accepted values | Purpose |\n| --- | --- | --- |\n| `DZENGI_ENV` | `demo` or `live` (default `demo`) | Selects the official adapter host. |\n| `DZENGI_API_VERSION` | `1` or `2`; defaults to `1` for demo and `2` for live | Demo v2 is rejected. |\n| `DZENGI_API_KEY` | blank | Signed-request key; public reads do not need it. |\n| `DZENGI_API_SECRET` | blank | HMAC secret; never returned in status or errors. |\n| `DZENGI_ALLOW_TRADE` | `false` (strict `true`/`false`) | Master mutation gate. |\n| `DZENGI_ALLOW_LIVE_TRADING` | `false` (strict `true`/`false`) | Required in addition to the master gate for live. |\n| `DZENGI_REQUIRE_CONFIRMATION` | `true` (strict `true`/`false`) | Requires `confirm: true` on every mutation; live trading enforces `true` at startup. |\n| `DZENGI_MAX_ORDER_NOTIONAL` | blank or positive plain decimal | Maximum order notional; mandatory when live trading is enabled. |\n| `DZENGI_MAX_LEVERAGE` | `1`, up to `1000` | Maximum requested leverage. |\n| `DZENGI_ALLOWED_SYMBOLS` | blank or comma-separated symbols | Optional trimmed, case-sensitive allowlist; copy symbols exactly from `dzengi_list_instruments`, including case and punctuation. |\n| `DZENGI_RECV_WINDOW_MS` | `5000`, integer `1..60000` | Signed request timing window. |\n| `DZENGI_TIMEOUT_MS` | `10000`, integer `100..120000` | HTTP timeout. |\n| `DZENGI_READ_RETRIES` | `2`, integer `0..5` | Bounded retries for transient reads only. |\n| `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. |\n| `DZENGI_ALLOW_CUSTOM_BASE_URL` | `false` (strict `true`/`false`) | Explicitly permits a non-official, fully trusted host. Custom non-loopback hosts must use HTTPS. |\n| `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`. |\n| `DZENGI_LOG_LEVEL` | `info`; `debug`, `info`, `warn`, or `error` | Secret-free stderr verbosity. |\n\n`DZENGI_LOG_LEVEL` controls lifecycle information only: `debug` and `info` show\nstartup information, while `warn` and `error` suppress it. Startup and\nshutdown errors always remain on stderr. Mutation audit events are independent\nof this filter and continue to be emitted to stderr and the configured audit\nfile when enabled. Runtime status reports only whether audit logging is enabled;\nit never returns the configured local audit path. Its `baseUrl` status is the\nconfigured endpoint origin only; endpoint paths are not returned.\n\nOnly set `DZENGI_BASE_URL` to an endpoint you fully trust: signed reads and every\nmutation send `X-MBX-APIKEY` and a query HMAC `signature` to that host. Keep the\ndefault official host unless a trusted test or gateway endpoint is required.\n\nChanging any environment value, especially either trade flag, takes effect\nonly after the MCP process is restarted because configuration is loaded once.\n\n## 6. Tool catalog\n\nThere are exactly 22 curated tools. Read tools have read-only MCP annotations;\nmutations have destructive/write annotations and always require a caller-owned\n`clientRequestId` plus a boolean `confirm` field.\n\n### Read-only market and runtime tools\n\n| Tool | What it does |\n| --- | --- |\n| `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. |\n| `dzengi_get_server_time` | Reads server time and refreshes the local clock offset. |\n| `dzengi_list_instruments` | Reads account-scoped `exchangeInfo` when both credentials are configured, otherwise public `exchangeInfo`, with bounded local pagination (`offset`, `limit`). |\n| `dzengi_get_ticker` | Reads an optional-symbol 24-hour ticker. |\n| `dzengi_get_order_book` | Reads bounded depth for a required `symbol` and optional `limit`. |\n| `dzengi_get_candles` | Reads bounded candles for `symbol`, `interval`, and optional time/price filters. |\n| `dzengi_get_trading_fees` | Reads optional-symbol fee information. |\n| `dzengi_get_trading_limits` | Reads optional-symbol broker limits. |\n| `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. |\n\n### Read-only account and lifecycle tools\n\n| Tool | What it does |\n| --- | --- |\n| `dzengi_get_account` | Reads signed account permissions and balances; optional `showZeroBalance`. |\n| `dzengi_list_open_orders` | Reads open orders, optionally filtered by `symbol`. |\n| `dzengi_get_order` | Reads a signed order by required `symbol` and `orderId`. |\n| `dzengi_list_positions` | Reads current leverage positions. |\n| `dzengi_list_trades` | Reads bounded signed trades for required `symbol` and optional time/limit filters. |\n| `dzengi_list_position_history` | Reads bounded position history with optional `symbol`, `from`, `to`, and `limit`. |\n| `dzengi_preflight_order` | Validates a proposed order against current metadata and policy without placing it. |\n\n### Guarded mutation tools\n\n| Tool | Financial operation and additional fields |\n| --- | --- |\n| `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`. |\n| `dzengi_cancel_order` | Cancels by required `symbol` and `orderId`; it never assumes `USD`. |\n| `dzengi_edit_order` | Edits an exchange order by required safety-only `symbol` and `orderId`; provide `price` and/or `expireTimestamp`. |\n| `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`. |\n| `dzengi_close_position` | Closes a leverage position by required safety-only `symbol` and `positionId`. |\n| `dzengi_update_position` | Updates position protection by required safety-only `symbol` and `positionId`; provide at least one stop/protection field. |\n\nEvery mutation is sent at most once by the transport. An ID must never be\nreused: while an entry remains in the bounded in-memory cache (up to 24 hours\nand 1,024 completed entries), an identical request replays and a different\nfinancial payload is rejected. Restart, TTL expiry, or capacity eviction\nremoves that local protection, so callers must reconcile before any new\nrequest.\n\nLifecycle mutations use `symbol` for allowlist and signed ownership checks, then\nomit it from the broker mutation payload. The signed order lookup is already\nscoped by the exact `{symbol, orderId}` query, so a single matching order record\nmay omit `symbol`; explicit mismatches and ambiguous records still fail closed.\nPosition records must include the exact symbol. Order edits with a replacement\nprice also re-check the associated quantity against the configured notional cap.\nUse the exact canonical symbol, including case and punctuation, returned by\n`dzengi_list_instruments` for all symbol-bearing tools.\n\n## 7. Enable demo mutations deliberately\n\nOnly do this with a demo account after the read-only checks pass. Replace every\n`REPLACE_WITH_...` value; these are intentionally fake placeholders, not\ncredentials or live symbols:\n\n```bash\nexport DZENGI_ENV=demo\nexport DZENGI_API_VERSION=1\nexport DZENGI_API_KEY=REPLACE_WITH_DEMO_API_KEY\nexport DZENGI_API_SECRET=REPLACE_WITH_DEMO_API_SECRET\nexport DZENGI_ALLOW_TRADE=true\nexport DZENGI_ALLOW_LIVE_TRADING=false\nexport DZENGI_REQUIRE_CONFIRMATION=true\nexport DZENGI_MAX_ORDER_NOTIONAL=10\nexport DZENGI_MAX_LEVERAGE=1\nexport DZENGI_ALLOWED_SYMBOLS=REPLACE_WITH_DEMO_SYMBOL\nnpm run build\nnpm start\n```\n\nThe server still requires `confirm: true` on each mutation and a new\n`clientRequestId`. Keep the notional and allowlist as small as practical. A\ndemo key should have no funding permission and should be bound and expired like\na live key.\n\n## 8. Enable live mutations only with all gates\n\nLive trading requires every item below at startup:\n\n1. `DZENGI_ENV=live` and a live API key/secret;\n2. `DZENGI_ALLOW_TRADE=true`;\n3. `DZENGI_ALLOW_LIVE_TRADING=true`;\n4. a positive `DZENGI_MAX_ORDER_NOTIONAL`;\n5. a deliberate `DZENGI_MAX_LEVERAGE` and, preferably, a narrow\n   `DZENGI_ALLOWED_SYMBOLS` list;\n6. `DZENGI_REQUIRE_CONFIRMATION=true` (enforced at startup) and `confirm: true`\n   per mutation; and\n7. Codex write approval enabled for the client session.\n\nExample shape, with deliberately fake credential placeholders:\n\n```bash\nexport DZENGI_ENV=live\nexport DZENGI_API_VERSION=2\nexport DZENGI_API_KEY=REPLACE_WITH_LIVE_API_KEY\nexport DZENGI_API_SECRET=REPLACE_WITH_LIVE_API_SECRET\nexport DZENGI_ALLOW_TRADE=true\nexport DZENGI_ALLOW_LIVE_TRADING=true\nexport DZENGI_REQUIRE_CONFIRMATION=true\nexport DZENGI_MAX_ORDER_NOTIONAL=10\nexport DZENGI_MAX_LEVERAGE=1\nexport DZENGI_ALLOWED_SYMBOLS=REPLACE_WITH_LIVE_SYMBOL\nnpm run build\nnpm start\n```\n\nThe example limit is not a recommendation; choose a limit appropriate to the\naccount and risk policy. Configuration rejects live trading without\n`DZENGI_MAX_ORDER_NOTIONAL`. Restart after every gate or credential change,\nthen inspect `dzengi_get_runtime_status` before considering a write.\n\n`DZENGI_MAX_ORDER_NOTIONAL` is measured in quote currency as quantity × limit or\nstop price (or the current market reference price for a market order). It is a\npre-dispatch cap, not a guarantee against execution price or slippage.\n\n## 9. Preflight, place, and reconcile\n\nDzengi's native `exchangeInfo` reports the price increment in top-level\n`tickSize` and may omit `minPrice`/`maxPrice` entirely. Preflight checks this\nincrement without treating absent optional price bounds as an API error.\nDeclared but incomplete price filters and malformed explicit bounds still\nproduce warnings. Quantity filters, broker minimum notional, configured caps,\nand confirmation requirements continue to apply independently.\n\nUse an obviously fake symbol in documentation examples and replace it only\nwith a symbol returned by `dzengi_list_instruments`:\n\n```json\n{\n  \"tool\": \"dzengi_preflight_order\",\n  \"arguments\": {\n    \"symbol\": \"REPLACE_WITH_DEMO_SYMBOL\",\n    \"type\": \"LIMIT\",\n    \"side\": \"BUY\",\n    \"quantity\": \"0.01\",\n    \"price\": \"1.00\",\n    \"confirm\": true\n  }\n}\n```\n\nPlacement must consume a fresh report with `allowed: true`. The mutation adds\nthe explicit confirmation and a new request ID; it is not a shell command and\nmust be issued through the MCP client:\n\n```json\n{\n  \"tool\": \"dzengi_place_order\",\n  \"arguments\": {\n    \"clientRequestId\": \"REPLACE_WITH_NEW_UUID\",\n    \"confirm\": true,\n    \"symbol\": \"REPLACE_WITH_DEMO_SYMBOL\",\n    \"type\": \"LIMIT\",\n    \"side\": \"BUY\",\n    \"quantity\": \"0.01\",\n    \"price\": \"1.00\"\n  }\n}\n```\n\nAn order mutation can cross the broker boundary before a timeout, network\nfailure, malformed response, or HTTP 5xx is observed. The server then returns\n`MUTATION_OUTCOME_UNKNOWN` with reconciliation guidance. Do **not** retry the\nmutation. Inspect, in order as applicable:\n\n- `dzengi_list_open_orders` for the symbol;\n- `dzengi_get_order` with the known `symbol` and `orderId`;\n- `dzengi_list_trades` for fills; and\n- `dzengi_list_positions` for position state.\n\nOn the same running process, repeating the identical `clientRequestId` returns\nthe cached unknown result without dispatching again. A different financial\npayload under that ID is rejected. The local guard is memory-only: it does not\nsurvive a process restart and is not a broker idempotency guarantee. Never\nreuse a mutation ID after restart; reconcile broker state first and choose a\nnew ID only for a deliberately new action.\n\n## 10. Emergency disablement and credential rotation\n\nThe emergency kill switch is to set **both** trade gates false and restart the\nprocess:\n\n```bash\nexport DZENGI_ALLOW_TRADE=false\nexport DZENGI_ALLOW_LIVE_TRADING=false\nnpm start\n```\n\nStop the existing process first (`Ctrl-C` for a foreground process). Changing\nthe variables without restarting does not change the active policy. Verify\n`dzengi_get_runtime_status` shows both gates disabled. If a key may have been\nexposed, revoke it in Dzengi immediately; do not rely on the process restart\nalone.\n\nFor rotation, generate a new key with the same least-privilege permissions,\nIP binding, 2FA, and expiry policy; update the external secret store or local\nenvironment; restart; run a public read and (if appropriate) a signed read;\nthen revoke the old key. Never print either value while checking the change.\n\n### Audit file operations\n\n`DZENGI_AUDIT_LOG_PATH` is optional. When set, the server appends one\nsecret-free JSONL start/terminal pair per mutation attempt; it does not persist\nordinary read responses or unrestricted broker payloads. Treat the file as\nsensitive operational data even though credentials and signatures are redacted.\nCreate an owner-only directory and file before starting the server:\n\n```bash\numask 077\ninstall -d -m 700 /var/lib/dzengi-mcp/audit\ntouch /var/lib/dzengi-mcp/audit/mutations.jsonl\nchmod 600 /var/lib/dzengi-mcp/audit/mutations.jsonl\nexport DZENGI_AUDIT_LOG_PATH=/var/lib/dzengi-mcp/audit/mutations.jsonl\n```\n\nThe writer is append-only at the application level. The configured path is\nread once at startup, while each event opens the configured path for an append;\nthe process does not automatically follow a renamed rotation target. To rotate\nsafely, stop the server (and let any in-flight write finish), move the old file\nto an owner-only archive, create a new `600` file, update\n`DZENGI_AUDIT_LOG_PATH`, and restart. Verify the new process's runtime status\nand that a deliberately chosen test mutation/readiness check writes to the new\npath; never use a live trade as a logging test. Set an operator-owned retention\nperiod, protect backups, and use the organization's approved secure-deletion\nprocedure when records expire. Audit files are local state and must never be\ncommitted, included in an npm tarball, or shipped to a support ticket without\nredaction.\n\n### Rollback to a known-good release\n\nSelect a previously verified package version and pin it instead of relying on\n`latest`. For example, the Codex entry can temporarily use an owner-approved\nversion tag:\n\n```toml\n[mcp_servers.dzengi]\ncommand = \"npx\"\nargs = [\"-y\", \"@alexbuzo/dzengi-mcp@0.1.0\"]\n```\n\nStop the existing MCP process or supervisor unit, restore the known-good\nenvironment file and safety gates (start with both trade gates `false`), and\nrestart the pinned version. Verify `dzengi_get_runtime_status`, then a public\nread such as `dzengi_get_server_time`; perform `dzengi_get_account` only when a\nsigned read is appropriate and credentials have been checked independently.\nDo not use a mutation to validate a rollback. If compromise is possible,\nrevoke the affected key, issue a replacement with the same least-privilege/IP/\n2FA/expiry policy, update the secret store, and restart again. npm publication\nand version promotion remain manual owner actions.\n\n## 11. Errors and unknown outcomes\n\nMCP failures are structured and set `isError: true`; the text and structured\nrepresentations contain the same safe error projection. The stable codes are:\n\n| Code | Meaning |\n| --- | --- |\n| `CONFIG_ERROR` | Invalid environment, unsupported API version, unsafe host, or missing live limit. |\n| `VALIDATION_ERROR` | Tool input, precision, required-field, account-selection, or preflight validation failed. |\n| `POLICY_DENIED` | Trade gate, live gate, confirmation, symbol allowlist, notional, or leverage policy denied the mutation. |\n| `AUTH_REQUIRED` | A signed read or mutation was requested without both credentials. |\n| `RATE_LIMITED` | Broker or local pacing rejected the request; read retries remain bounded. |\n| `DZENGI_HTTP_ERROR` | A non-success HTTP response or transport failure was classified as an HTTP error. |\n| `DZENGI_API_ERROR` | Dzengi returned a non-success API envelope or malformed success payload. |\n| `MUTATION_OUTCOME_UNKNOWN` | A dispatched mutation may have succeeded; reconcile before any new action. |\n\nOnly idempotent reads retry bounded transient statuses (`408`, `429`, `500`,\n`502`, `503`, `504`). Writes are never automatically retried, including after\ntimestamp, timeout, network, malformed-response, or 5xx failures. The shared\nlimiter stays below Dzengi's documented 10 requests/second limit and applies a\nseparate margin for `openOrders`.\n\n## 12. Development, tests, and owner release commands\n\nRequirements: Node.js 20 or newer and npm. The offline development gate is:\n\n```bash\nnpm ci\nnpm test\nnpm run typecheck\nnpm run lint\nnpm run build\nnpm run verify:stdio\nnpm run verify:pack\n```\n\n`npm run verify` runs the test, type, lint, build, stdio, and package gates in\none command. `npm run dev` runs the TypeScript entry point for local work;\n`npm run clean` removes `dist`. `npm run update:openapi` refreshes the checked-\nin official Swagger snapshot and `npm run check:openapi` checks for drift; both\nare maintainer commands that need network access and their source snapshots are\nnot shipped in the npm payload. `npm run format` formats the repository.\n\nBefore an owner release, inspect the dry-run package and then use the existing\nowner npm commands:\n\n```bash\nnpm pack --dry-run\nnpm publish --access public\n```\n\nThis repository's Task 11 verification does **not** publish. The package's\n`prepack` build and `prepublishOnly` test/type/lint/build hooks provide an\nadditional release guard; `npm run verify:pack` invokes dry-run packing with\n`--ignore-scripts` so the verifier cannot recursively invoke those hooks.\n\n## 13. Official documentation\n\n- [Dzengi API landing page](https://dzengi.com/api)\n- [Dzengi Swagger UI](https://apitradedoc.dzengi.com/swagger-ui.html)\n- [Dzengi public Swagger JSON](https://apitradedoc.dzengi.com/v2/api-docs?group=public-api)\n- [Dzengi General REST API Information](https://dzengi.com/general-rest-api-information)\n- [Dzengi API Get Started](https://dzengi.com/api-get-started)\n- [Dzengi API Changelog](https://dzengi.com/api-changelog)\n- [OpenAI Codex MCP configuration](https://developers.openai.com/codex/mcp)\n\nWhen broker behavior and this README differ, verify the official Dzengi\ndocumentation and the checked-in Swagger snapshot before changing a limit or\nendpoint. The package intentionally favors a fail-closed result over guessing.\n","readmeFilename":"README.md"}