@usenami/signer-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,104 @@
1
+ /**
2
+ * OKX v5 account parser.
3
+ *
4
+ * OKX splits balance and positions across TWO endpoints:
5
+ * GET /api/v5/account/balance → equity + free margin
6
+ * GET /api/v5/account/positions → open positions
7
+ *
8
+ * For the Option-A architecture this means signer's `/account/<venue>` for
9
+ * OKX may need to return TWO signed requests (or one composite). For v0 we
10
+ * accept a combined raw payload:
11
+ *
12
+ * {
13
+ * "balance": { ...OKX /balance response... },
14
+ * "positions": { ...OKX /positions response... }
15
+ * }
16
+ *
17
+ * If only `balance` is present (positions response missing), we still emit
18
+ * a valid NormalizedAccount with empty positions — the agent can decide.
19
+ *
20
+ * OKX response wrapper: { "code": "0", "msg": "", "data": [ {...} ] }
21
+ * Single-element data arrays are typical for these endpoints.
22
+ *
23
+ * Docs:
24
+ * https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-balance
25
+ * https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-positions
26
+ *
27
+ * Quirks:
28
+ * - OKX returns numbers as strings (like Binance).
29
+ * - Empty equity field returns "" not null.
30
+ * - Positions array can include zeroed-out rows after closes; filter them.
31
+ * - `instId` is OKX's symbol field (e.g. "BTC-USDT-SWAP").
32
+ */
33
+ function toNum(v, fallback = 0) {
34
+ if (typeof v === "number")
35
+ return Number.isFinite(v) ? v : fallback;
36
+ if (typeof v === "string" && v.length > 0) {
37
+ const n = parseFloat(v);
38
+ return Number.isFinite(n) ? n : fallback;
39
+ }
40
+ return fallback;
41
+ }
42
+ function unwrapOkxArray(payload) {
43
+ // Standard shape: { code: "0", data: [ {...} ] }
44
+ const obj = payload;
45
+ if (!obj || !Array.isArray(obj.data))
46
+ return null;
47
+ const first = obj.data[0];
48
+ return first ?? null;
49
+ }
50
+ export const parseOkxAccount = (raw) => {
51
+ const wrap = raw ?? {};
52
+ const balanceWrap = unwrapOkxArray(wrap.balance);
53
+ const positionsWrap = wrap.positions;
54
+ // Equity + free margin from /balance.
55
+ // OKX returns multi-currency `details: [...]` per account; we sum USDT/USDC
56
+ // available balances and use totalEq (already USD-normalized).
57
+ const equity = toNum(balanceWrap?.totalEq);
58
+ let freeMargin = 0;
59
+ const details = Array.isArray(balanceWrap?.details) ? balanceWrap?.details : [];
60
+ for (const d of details) {
61
+ const ccy = String(d.ccy ?? "");
62
+ if (ccy === "USDT" || ccy === "USDC" || ccy === "USD") {
63
+ // availBal is the spendable balance in that currency. Sum across stable
64
+ // collateral types — close enough for v0 USD-equivalent.
65
+ freeMargin += toNum(d.availBal);
66
+ }
67
+ }
68
+ // Fallback: if no stable collateral row, use adjEq as a rough proxy.
69
+ if (freeMargin === 0) {
70
+ freeMargin = toNum(balanceWrap?.adjEq);
71
+ }
72
+ // Positions from /positions.
73
+ const positions = [];
74
+ if (positionsWrap && Array.isArray(positionsWrap.data)) {
75
+ for (const p of positionsWrap.data) {
76
+ const qty = toNum(p.pos);
77
+ if (qty === 0)
78
+ continue;
79
+ const symbol = String(p.instId ?? "");
80
+ if (symbol === "")
81
+ continue;
82
+ positions.push({
83
+ symbol,
84
+ qty,
85
+ entry_price: toNum(p.avgPx),
86
+ unrealized_pnl: toNum(p.upl),
87
+ mark_price: p.markPx !== undefined ? toNum(p.markPx) : undefined,
88
+ });
89
+ }
90
+ }
91
+ // OKX gives uTime / cTime (timestamps in ms) — use balance.uTime when present.
92
+ const ts = balanceWrap?.uTime;
93
+ const updated_at = typeof ts === "string" && ts.length > 0
94
+ ? new Date(parseInt(ts, 10)).toISOString()
95
+ : new Date(0).toISOString();
96
+ return {
97
+ venue: "okx",
98
+ equity_usd: equity,
99
+ free_margin_usd: freeMargin,
100
+ positions,
101
+ updated_at,
102
+ };
103
+ };
104
+ //# sourceMappingURL=okx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"okx.js","sourceRoot":"","sources":["../../src/parsers/okx.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Common shapes for venue parsers under the Option-A architecture (signer
3
+ * returns signed read request, signer-mcp executes it + parses).
4
+ *
5
+ * Per signer's 2026-05-31T2150 schema-coordination report. The MCP tool
6
+ * contract returns this normalized shape — venue-specific quirks live
7
+ * inside each parser.
8
+ */
9
+ export interface NormalizedAccount {
10
+ /** Venue id (binance, okx, asterdex) — echoed for agent introspection. */
11
+ venue: string;
12
+ /**
13
+ * Total margin balance in USD-equivalent. Some venues return USDT-collat;
14
+ * we treat USDT as USD for v0 (acceptable for the demo, real conversion
15
+ * via mark price comes in v0.1).
16
+ */
17
+ equity_usd: number;
18
+ /**
19
+ * Free margin = equity - used by open positions. Used for `place_order`
20
+ * pre-checks ("do I have margin?").
21
+ */
22
+ free_margin_usd: number;
23
+ /** Per-symbol open positions. Empty array when flat. */
24
+ positions: NormalizedPosition[];
25
+ /**
26
+ * Server-side timestamp from the venue. ISO 8601 if the venue gives one,
27
+ * otherwise a normalized fetch timestamp (parser sets it).
28
+ */
29
+ updated_at: string;
30
+ }
31
+ export interface NormalizedPosition {
32
+ symbol: string;
33
+ /**
34
+ * Signed quantity in base asset. Positive = long, negative = short. Zero
35
+ * positions are filtered out before returning.
36
+ */
37
+ qty: number;
38
+ /** Volume-weighted average entry price. */
39
+ entry_price: number;
40
+ /** Unrealized PnL in venue's quote/collat currency. */
41
+ unrealized_pnl: number;
42
+ /** Mark price if the venue provides it (Binance does, OKX does, Asterdex partial). */
43
+ mark_price?: number;
44
+ }
45
+ export interface SignedRequest {
46
+ venue: string;
47
+ method: "GET" | "POST" | "DELETE";
48
+ url: string;
49
+ headers: Record<string, string>;
50
+ body?: string;
51
+ }
52
+ /**
53
+ * A parser turns a venue's raw account-endpoint response into our normalized
54
+ * shape. Each parser knows ONE venue's quirks; the dispatcher in
55
+ * parsers/index.ts picks the right one based on venue id.
56
+ *
57
+ * Parsers MUST be tolerant: missing fields → return 0 or [], never throw.
58
+ * Throwing in a parser cascades to the tool error path, which kills the
59
+ * agent's ability to call place_order even when the account read partially
60
+ * worked.
61
+ */
62
+ export type AccountParser = (raw: unknown) => NormalizedAccount;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Common shapes for venue parsers under the Option-A architecture (signer
3
+ * returns signed read request, signer-mcp executes it + parses).
4
+ *
5
+ * Per signer's 2026-05-31T2150 schema-coordination report. The MCP tool
6
+ * contract returns this normalized shape — venue-specific quirks live
7
+ * inside each parser.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/parsers/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@usenami/signer-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Sign CEX orders from any MCP-aware AI agent (Claude Desktop, Cursor, ElizaOS) with keys that never leave an AWS Nitro Enclave.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "signer-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE",
14
+ "CHANGELOG.md",
15
+ "server.json"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "prepare": "npm run build",
20
+ "start": "node dist/index.js",
21
+ "dev": "tsx src/index.ts",
22
+ "test": "vitest run",
23
+ "smoke": "npm run build && tsx scripts/smoke-test.ts"
24
+ },
25
+ "keywords": [
26
+ "mcp",
27
+ "model-context-protocol",
28
+ "trading",
29
+ "binance",
30
+ "okx",
31
+ "perpetuals",
32
+ "attested-compute",
33
+ "aws-nitro-enclaves",
34
+ "claude",
35
+ "cursor",
36
+ "elizaos",
37
+ "ai-agents",
38
+ "usenami",
39
+ "signer"
40
+ ],
41
+ "author": "Usenami",
42
+ "license": "MIT",
43
+ "homepage": "https://usenami.io/signer",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/namixai/namixai-terminal.git",
47
+ "directory": "signer-mcp"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/namixai/namixai-terminal/issues"
51
+ },
52
+ "engines": {
53
+ "node": ">=18"
54
+ },
55
+ "dependencies": {
56
+ "@modelcontextprotocol/sdk": "^1.21.0",
57
+ "zod": "^3.23.0"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^22.19.19",
61
+ "tsx": "^4.19.0",
62
+ "typescript": "^5.6.0",
63
+ "vitest": "^4.1.7"
64
+ }
65
+ }
package/server.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "$schema": "https://registry.modelcontextprotocol.io/schemas/server.json",
3
+ "name": "@usenami/signer-mcp",
4
+ "version": "0.1.0",
5
+ "description": "Sign CEX orders (Binance, OKX, Asterdex) from any MCP-aware AI agent with keys that never leave an AWS Nitro Enclave.",
6
+ "homepage": "https://usenami.io/signer",
7
+ "license": "MIT",
8
+ "tags": [
9
+ "trading",
10
+ "binance",
11
+ "okx",
12
+ "perpetuals",
13
+ "attested-compute",
14
+ "aws-nitro-enclaves",
15
+ "signer",
16
+ "ai-agents"
17
+ ],
18
+ "categories": ["trading", "finance", "security"],
19
+ "transport": {
20
+ "type": "stdio",
21
+ "command": "npx",
22
+ "args": ["-y", "@usenami/signer-mcp"]
23
+ },
24
+ "env_schema": {
25
+ "SIGNER_GATEWAY_URL": {
26
+ "description": "Override the Signer gateway URL (default: https://signer.usenami.io)",
27
+ "required": false
28
+ },
29
+ "SIGNER_API_TOKEN": {
30
+ "description": "Bearer token issued by usenami.io/signer. Required for everything except list_venues.",
31
+ "required": false,
32
+ "secret": true
33
+ }
34
+ },
35
+ "tools": [
36
+ {
37
+ "name": "list_venues",
38
+ "description": "List the venues supported by this Signer. Read-only static manifest — no gateway call.",
39
+ "readOnly": true
40
+ },
41
+ {
42
+ "name": "get_attestation",
43
+ "description": "Return the AWS Nitro attestation document (PCR0+sig) for the running enclave.",
44
+ "readOnly": true
45
+ },
46
+ {
47
+ "name": "get_account",
48
+ "description": "Return equity, free margin, and open positions for a venue.",
49
+ "readOnly": true
50
+ },
51
+ {
52
+ "name": "place_order",
53
+ "description": "Sign and submit a single market or limit order. Enclave enforces policy caps.",
54
+ "readOnly": false
55
+ },
56
+ {
57
+ "name": "cancel_order",
58
+ "description": "Cancel an outstanding order by venue order_id. Idempotent.",
59
+ "readOnly": false
60
+ }
61
+ ]
62
+ }