@coinrithm/mcp-trading 0.7.8 → 0.7.10
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.
- package/CHANGELOG.md +121 -0
- package/README.md +93 -69
- package/dist/agent/capitalSizing.js +17 -4
- package/dist/agent/cli.js +11 -1
- package/dist/agent/client.d.ts +8 -1
- package/dist/agent/client.js +98 -39
- package/dist/agent/configurationWarnings.d.ts +1 -0
- package/dist/agent/configurationWarnings.js +21 -0
- package/dist/agent/decisionReceipt.d.ts +4 -1
- package/dist/agent/decisionReceipt.js +27 -3
- package/dist/agent/decisionValidator.js +4 -0
- package/dist/agent/engine.d.ts +1 -1
- package/dist/agent/engine.js +1 -1
- package/dist/agent/entryPredicates.d.ts +10 -0
- package/dist/agent/entryPredicates.js +67 -0
- package/dist/agent/gate.js +14 -11
- package/dist/agent/opportunityReporter.d.ts +16 -0
- package/dist/agent/opportunityReporter.js +38 -0
- package/dist/agent/prompt.js +5 -0
- package/dist/agent/providers.d.ts +2 -1
- package/dist/agent/providers.js +109 -19
- package/dist/agent/reconcileObservation.d.ts +2 -0
- package/dist/agent/reconcileObservation.js +33 -0
- package/dist/agent/runner.js +32 -74
- package/dist/agent/skill.js +6 -0
- package/dist/agent/skillValidator.js +3 -0
- package/dist/agent/state.js +17 -2
- package/dist/agent/strictLint.js +1 -0
- package/dist/agent/templates.js +34 -30
- package/dist/agent/types.d.ts +1 -0
- package/dist/agent/util.js +3 -3
- package/dist/client.js +3 -2
- package/dist/http.d.ts +7 -1
- package/dist/http.js +36 -16
- package/dist/httpCompletion.d.ts +33 -0
- package/dist/httpCompletion.js +220 -0
- package/dist/retryAfter.d.ts +1 -0
- package/dist/retryAfter.js +16 -0
- package/dist/tools.js +19 -1
- package/package.json +13 -2
package/dist/http.js
CHANGED
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
// forwards as `extra.authInfo`, giving requestKey() a second source. Either
|
|
25
25
|
// way the caller's own key — and only that key — is used for their tool call.
|
|
26
26
|
// - Unauthenticated MCP initialization and tool-list introspection are allowed
|
|
27
|
-
// so registries can verify the server.
|
|
28
|
-
// a structured 401
|
|
27
|
+
// so registries can verify the server. Public data tools are also keyless;
|
|
28
|
+
// protected tools return a structured 401 when their key is missing.
|
|
29
29
|
//
|
|
30
30
|
// Config (env):
|
|
31
31
|
// COINRITHM_API_URL (optional) upstream base URL (default production).
|
|
@@ -35,15 +35,23 @@
|
|
|
35
35
|
// the correct isolation model for a multi-user, per-request-keyed surface — no
|
|
36
36
|
// session state is shared between users.
|
|
37
37
|
import express from "express";
|
|
38
|
+
import { pathToFileURL } from "node:url";
|
|
38
39
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
39
40
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
40
41
|
import { CoinRithmClient, bearerFromHeader, loadHttpConfig, log, } from "./client.js";
|
|
41
42
|
import { registerTools } from "./tools.js";
|
|
42
43
|
import { SERVER_VERSION } from "./version.js";
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
import { observeHttpCompletion, } from "./httpCompletion.js";
|
|
45
|
+
// Factory permits isolated localhost SDK tests without opening a listener on import.
|
|
46
|
+
export function createHttpApp(client, options = {}) {
|
|
46
47
|
const app = express();
|
|
48
|
+
const completions = new WeakMap();
|
|
49
|
+
app.use((req, res, next) => {
|
|
50
|
+
if (req.method === "POST" && /^\/mcp\/?$/i.test(req.path)) {
|
|
51
|
+
completions.set(res, observeHttpCompletion(req, res, options.completionLogger));
|
|
52
|
+
}
|
|
53
|
+
next();
|
|
54
|
+
});
|
|
47
55
|
app.use(express.json());
|
|
48
56
|
// Lightweight, unauthenticated liveness probe (handy for Coolify/uptime checks).
|
|
49
57
|
// Keep `/healthz` as the deployment contract and expose `/health` as a
|
|
@@ -110,8 +118,8 @@ async function main() {
|
|
|
110
118
|
// Per-request auth: read THIS caller's key from the Authorization header,
|
|
111
119
|
// or from Smithery's non-reserved forwarding header.
|
|
112
120
|
// It is optional at the transport layer so registries can initialize the
|
|
113
|
-
// server and list tool schemas.
|
|
114
|
-
// a structured 401 if
|
|
121
|
+
// server and list tool schemas. Public data tools are keyless; protected
|
|
122
|
+
// tool handlers return a structured 401 if their key is missing.
|
|
115
123
|
const apiKey = bearerFromHeader(req.headers.authorization) ??
|
|
116
124
|
bearerFromHeader(req.headers["x-coinrithm-api-key"]);
|
|
117
125
|
// Belt-and-suspenders: also expose the token via the SDK's authInfo channel.
|
|
@@ -120,11 +128,13 @@ async function main() {
|
|
|
120
128
|
if (apiKey) {
|
|
121
129
|
req.auth = { token: apiKey, clientId: "coinrithm-key", scopes: [] };
|
|
122
130
|
}
|
|
123
|
-
const server =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
const server = options.createServer?.() ??
|
|
132
|
+
new McpServer({
|
|
133
|
+
name: "coinrithm-trading",
|
|
134
|
+
version: SERVER_VERSION,
|
|
135
|
+
});
|
|
136
|
+
if (!options.createServer)
|
|
137
|
+
registerTools(server, client);
|
|
128
138
|
const transport = new StreamableHTTPServerTransport({
|
|
129
139
|
sessionIdGenerator: undefined, // stateless: no cross-request/user state
|
|
130
140
|
});
|
|
@@ -134,6 +144,7 @@ async function main() {
|
|
|
134
144
|
});
|
|
135
145
|
try {
|
|
136
146
|
await server.connect(transport);
|
|
147
|
+
completions.get(res)?.attach(transport);
|
|
137
148
|
// The transport reads req.headers (→ extra.requestInfo) and req.auth
|
|
138
149
|
// (→ extra.authInfo); tools.ts picks up the caller's key from there.
|
|
139
150
|
await transport.handleRequest(req, res, req.body);
|
|
@@ -149,13 +160,22 @@ async function main() {
|
|
|
149
160
|
}
|
|
150
161
|
}
|
|
151
162
|
});
|
|
163
|
+
return app;
|
|
164
|
+
}
|
|
165
|
+
async function main() {
|
|
166
|
+
const config = loadHttpConfig(); // no global key — keys arrive per request
|
|
167
|
+
const client = new CoinRithmClient(config); // constructed WITHOUT a default key
|
|
168
|
+
const app = createHttpApp(client);
|
|
152
169
|
const port = Number(process.env.PORT) || 8787;
|
|
153
170
|
app.listen(port, () => {
|
|
154
171
|
log(`HTTP MCP listening on :${port}/mcp (multi-user, per-request key). ` +
|
|
155
172
|
`upstream=${config.baseUrl}. Paper only.`);
|
|
156
173
|
});
|
|
157
174
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
175
|
+
if (process.argv[1] &&
|
|
176
|
+
import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
177
|
+
main().catch((err) => {
|
|
178
|
+
log("fatal:", err instanceof Error ? err.message : err);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
3
|
+
export declare const COMPLETION_TOOL_NAMES: readonly ["whoami", "get_portfolio", "get_wallet", "list_open_orders", "get_positions", "resolve_symbol", "get_equity_curve", "get_my_trades", "get_market_context", "get_candles", "discover_pm_markets", "get_performance", "get_agent_ledger", "export_agent_ledger", "export_run_evidence", "get_arena_leaderboard", "get_arena_agent", "futures_quote", "pm_quote", "spot_quote", "place_spot_order", "cancel_spot_order", "open_futures_position", "set_futures_sl_tp", "close_futures_position", "open_pm_position", "report_pm_opportunity", "pm_data_overview", "pm_data_sources", "pm_data_sources_health", "pm_data_events", "pm_data_event", "pm_data_whales", "pm_data_disagreements", "pm_data_calibration", "pm_data_canonical", "pm_data_volume_history", "get_crypto_movers"];
|
|
4
|
+
type Operation = "initialize" | "tools_list" | "tools_call" | "notification" | "other" | "invalid";
|
|
5
|
+
type Outcome = "result" | "tool_error" | "protocol_error" | "no_response" | "not_applicable";
|
|
6
|
+
type ToolName = (typeof COMPLETION_TOOL_NAMES)[number] | "unknown" | null;
|
|
7
|
+
type RpcCompletion = {
|
|
8
|
+
operation: Operation;
|
|
9
|
+
tool: ToolName;
|
|
10
|
+
rpc_outcome: Outcome;
|
|
11
|
+
result_http_status: number | null;
|
|
12
|
+
result_ok: boolean | null;
|
|
13
|
+
};
|
|
14
|
+
export type HttpCompletionRecord = Readonly<RpcCompletion & {
|
|
15
|
+
event: "mcp_completion";
|
|
16
|
+
schema_version: 1;
|
|
17
|
+
completed_at: string;
|
|
18
|
+
duration_ms: number;
|
|
19
|
+
service_version: string;
|
|
20
|
+
transport: "streamable_http";
|
|
21
|
+
credential_supplied: boolean;
|
|
22
|
+
http_status: number | null;
|
|
23
|
+
delivery: "finished" | "aborted";
|
|
24
|
+
}>;
|
|
25
|
+
export type CompletionLogger = (line: string) => void;
|
|
26
|
+
/** Attach before JSON parsing; malformed/rejected HTTP requests remain invalid,
|
|
27
|
+
* no_response, with their actual HTTP status. Do not read bodies or raw errors.
|
|
28
|
+
* Authentication and caller origin are deliberately NOT inferred from headers.
|
|
29
|
+
*/
|
|
30
|
+
export declare function observeHttpCompletion(req: IncomingMessage, res: ServerResponse, logger?: CompletionLogger): {
|
|
31
|
+
attach: (transport: Transport) => void;
|
|
32
|
+
};
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { bearerFromHeader, log } from "./client.js";
|
|
2
|
+
import { SERVER_VERSION } from "./version.js";
|
|
3
|
+
// Explicit, server-owned dimensions; tests compare this with actual tools/list.
|
|
4
|
+
// Never retain a caller's unknown tool name, even truncated or hashed.
|
|
5
|
+
export const COMPLETION_TOOL_NAMES = [
|
|
6
|
+
"whoami",
|
|
7
|
+
"get_portfolio",
|
|
8
|
+
"get_wallet",
|
|
9
|
+
"list_open_orders",
|
|
10
|
+
"get_positions",
|
|
11
|
+
"resolve_symbol",
|
|
12
|
+
"get_equity_curve",
|
|
13
|
+
"get_my_trades",
|
|
14
|
+
"get_market_context",
|
|
15
|
+
"get_candles",
|
|
16
|
+
"discover_pm_markets",
|
|
17
|
+
"get_performance",
|
|
18
|
+
"get_agent_ledger",
|
|
19
|
+
"export_agent_ledger",
|
|
20
|
+
"export_run_evidence",
|
|
21
|
+
"get_arena_leaderboard",
|
|
22
|
+
"get_arena_agent",
|
|
23
|
+
"futures_quote",
|
|
24
|
+
"pm_quote",
|
|
25
|
+
"spot_quote",
|
|
26
|
+
"place_spot_order",
|
|
27
|
+
"cancel_spot_order",
|
|
28
|
+
"open_futures_position",
|
|
29
|
+
"set_futures_sl_tp",
|
|
30
|
+
"close_futures_position",
|
|
31
|
+
"open_pm_position",
|
|
32
|
+
"report_pm_opportunity",
|
|
33
|
+
"pm_data_overview",
|
|
34
|
+
"pm_data_sources",
|
|
35
|
+
"pm_data_sources_health",
|
|
36
|
+
"pm_data_events",
|
|
37
|
+
"pm_data_event",
|
|
38
|
+
"pm_data_whales",
|
|
39
|
+
"pm_data_disagreements",
|
|
40
|
+
"pm_data_calibration",
|
|
41
|
+
"pm_data_canonical",
|
|
42
|
+
"pm_data_volume_history",
|
|
43
|
+
"get_crypto_movers",
|
|
44
|
+
];
|
|
45
|
+
const toolNames = new Set(COMPLETION_TOOL_NAMES);
|
|
46
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value)
|
|
47
|
+
? value
|
|
48
|
+
: undefined;
|
|
49
|
+
const status = (value) => typeof value === "number" &&
|
|
50
|
+
Number.isInteger(value) &&
|
|
51
|
+
(value === 0 || (value >= 100 && value <= 599))
|
|
52
|
+
? value
|
|
53
|
+
: null;
|
|
54
|
+
const empty = (operation = "invalid", tool = null) => ({
|
|
55
|
+
operation,
|
|
56
|
+
tool,
|
|
57
|
+
rpc_outcome: "no_response",
|
|
58
|
+
result_http_status: null,
|
|
59
|
+
result_ok: null,
|
|
60
|
+
});
|
|
61
|
+
/** Attach before JSON parsing; malformed/rejected HTTP requests remain invalid,
|
|
62
|
+
* no_response, with their actual HTTP status. Do not read bodies or raw errors.
|
|
63
|
+
* Authentication and caller origin are deliberately NOT inferred from headers.
|
|
64
|
+
*/
|
|
65
|
+
export function observeHttpCompletion(req, res, logger = (line) => log(line)) {
|
|
66
|
+
const started = performance.now();
|
|
67
|
+
const credentialSupplied = Boolean(bearerFromHeader(req.headers.authorization) ??
|
|
68
|
+
bearerFromHeader(req.headers["x-coinrithm-api-key"]));
|
|
69
|
+
const records = [];
|
|
70
|
+
// IDs are transient correlations only, never copied into records/logs. Clear
|
|
71
|
+
// on completion so a late tool callback cannot retain IDs or emit twice.
|
|
72
|
+
const pending = new Map();
|
|
73
|
+
let completed = false;
|
|
74
|
+
let delivery;
|
|
75
|
+
let sendsInFlight = 0;
|
|
76
|
+
const emit = () => {
|
|
77
|
+
if (completed || !delivery || (delivery === "finished" && sendsInFlight))
|
|
78
|
+
return;
|
|
79
|
+
completed = true;
|
|
80
|
+
res.off("finish", onFinish);
|
|
81
|
+
res.off("close", onClose);
|
|
82
|
+
res.off("error", onAbort);
|
|
83
|
+
const elapsed = performance.now() - started;
|
|
84
|
+
const duration = Number.isFinite(elapsed)
|
|
85
|
+
? Math.max(0, Math.round(elapsed * 1000) / 1000)
|
|
86
|
+
: 0;
|
|
87
|
+
const completedAt = new Date().toISOString();
|
|
88
|
+
// A pre-header disconnect has no HTTP status; Node's default 200 is not one.
|
|
89
|
+
const httpStatus = res.headersSent ? status(res.statusCode) : null;
|
|
90
|
+
const snapshots = records.length ? records : [empty()];
|
|
91
|
+
for (const rpc of snapshots) {
|
|
92
|
+
const record = {
|
|
93
|
+
event: "mcp_completion",
|
|
94
|
+
schema_version: 1,
|
|
95
|
+
completed_at: completedAt,
|
|
96
|
+
duration_ms: duration,
|
|
97
|
+
service_version: SERVER_VERSION,
|
|
98
|
+
transport: "streamable_http",
|
|
99
|
+
...rpc,
|
|
100
|
+
credential_supplied: credentialSupplied,
|
|
101
|
+
http_status: httpStatus,
|
|
102
|
+
delivery,
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
logger(JSON.stringify(record));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Observability must never change a tool response or recurse into logging.
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
records.length = 0;
|
|
112
|
+
pending.clear();
|
|
113
|
+
};
|
|
114
|
+
const onFinish = () => {
|
|
115
|
+
delivery = "finished";
|
|
116
|
+
emit();
|
|
117
|
+
};
|
|
118
|
+
const onAbort = () => {
|
|
119
|
+
delivery = "aborted";
|
|
120
|
+
// A computed response in an interrupted stream is not confirmed delivered.
|
|
121
|
+
// In particular, a still-pending send may reject after this close event.
|
|
122
|
+
for (const rpc of records) {
|
|
123
|
+
if (rpc.rpc_outcome !== "not_applicable") {
|
|
124
|
+
rpc.rpc_outcome = "no_response";
|
|
125
|
+
rpc.result_http_status = null;
|
|
126
|
+
rpc.result_ok = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
emit();
|
|
130
|
+
};
|
|
131
|
+
const onClose = () => {
|
|
132
|
+
if (!res.writableFinished)
|
|
133
|
+
onAbort();
|
|
134
|
+
else
|
|
135
|
+
onFinish();
|
|
136
|
+
};
|
|
137
|
+
res.once("finish", onFinish);
|
|
138
|
+
res.once("close", onClose);
|
|
139
|
+
res.once("error", onAbort);
|
|
140
|
+
const incoming = (message) => {
|
|
141
|
+
if (completed)
|
|
142
|
+
return;
|
|
143
|
+
if (!("method" in message)) {
|
|
144
|
+
records.push({ ...empty("other"), rpc_outcome: "not_applicable" });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!("id" in message)) {
|
|
148
|
+
records.push({ ...empty("notification"), rpc_outcome: "not_applicable" });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const operation = message.method === "initialize"
|
|
152
|
+
? "initialize"
|
|
153
|
+
: message.method === "tools/list"
|
|
154
|
+
? "tools_list"
|
|
155
|
+
: message.method === "tools/call"
|
|
156
|
+
? "tools_call"
|
|
157
|
+
: "other";
|
|
158
|
+
const name = operation === "tools_call" ? object(message.params)?.name : undefined;
|
|
159
|
+
const tool = operation !== "tools_call"
|
|
160
|
+
? null
|
|
161
|
+
: typeof name === "string" && toolNames.has(name)
|
|
162
|
+
? name
|
|
163
|
+
: "unknown";
|
|
164
|
+
const rpc = empty(operation, tool);
|
|
165
|
+
records.push(rpc);
|
|
166
|
+
pending.set(message.id, [...(pending.get(message.id) ?? []), rpc]);
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
attach(transport) {
|
|
170
|
+
// Call AFTER server.connect (which installs SDK callbacks), before handling
|
|
171
|
+
// this request. Observe only SDK-accepted messages, not unvalidated bodies.
|
|
172
|
+
const onmessage = transport.onmessage;
|
|
173
|
+
transport.onmessage = (message, extra) => {
|
|
174
|
+
incoming(message);
|
|
175
|
+
onmessage?.(message, extra);
|
|
176
|
+
};
|
|
177
|
+
const send = transport.send.bind(transport);
|
|
178
|
+
transport.send = async (message, options) => {
|
|
179
|
+
const matches = "id" in message ? pending.get(message.id) : undefined;
|
|
180
|
+
// Duplicate IDs are ambiguous; never attribute one response twice.
|
|
181
|
+
const rpc = !completed && matches?.length === 1 ? matches[0] : undefined;
|
|
182
|
+
if (!rpc || (!("result" in message) && !("error" in message))) {
|
|
183
|
+
return send(message, options);
|
|
184
|
+
}
|
|
185
|
+
// This is the SDK's FINAL result, after input/output schema validation.
|
|
186
|
+
rpc.result_http_status = null;
|
|
187
|
+
rpc.result_ok = null;
|
|
188
|
+
if ("error" in message)
|
|
189
|
+
rpc.rpc_outcome = "protocol_error";
|
|
190
|
+
else {
|
|
191
|
+
const result = object(message.result);
|
|
192
|
+
const structured = object(result?.structuredContent);
|
|
193
|
+
rpc.rpc_outcome =
|
|
194
|
+
rpc.operation === "tools_call" && result?.isError === true
|
|
195
|
+
? "tool_error"
|
|
196
|
+
: "result";
|
|
197
|
+
if (rpc.operation === "tools_call") {
|
|
198
|
+
rpc.result_http_status = status(structured?.httpStatus);
|
|
199
|
+
rpc.result_ok =
|
|
200
|
+
typeof structured?.ok === "boolean" ? structured.ok : null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
sendsInFlight += 1;
|
|
204
|
+
try {
|
|
205
|
+
return await send(message, options);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
rpc.rpc_outcome = "no_response";
|
|
209
|
+
rpc.result_http_status = null;
|
|
210
|
+
rpc.result_ok = null;
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
sendsInFlight -= 1;
|
|
215
|
+
emit();
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function retryAfterSeconds(value: string | null, now?: number): number | undefined;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Retry-After permits a delay in seconds or an HTTP date. Missing/invalid
|
|
2
|
+
// evidence stays absent, so each caller can apply its explicit fallback.
|
|
3
|
+
export function retryAfterSeconds(value, now = Date.now()) {
|
|
4
|
+
const raw = value?.trim();
|
|
5
|
+
if (!raw)
|
|
6
|
+
return undefined;
|
|
7
|
+
if (/^\d+(?:\.\d+)?$/.test(raw)) {
|
|
8
|
+
const seconds = Number(raw);
|
|
9
|
+
return Number.isFinite(seconds) ? seconds : undefined;
|
|
10
|
+
}
|
|
11
|
+
// Avoid Date.parse treating malformed numeric delays (e.g. "-1") as dates.
|
|
12
|
+
if (!/^[A-Za-z]{3}(?:,|day,|\s)/.test(raw))
|
|
13
|
+
return undefined;
|
|
14
|
+
const date = Date.parse(raw);
|
|
15
|
+
return Number.isFinite(date) ? Math.max(0, (date - now) / 1000) : undefined;
|
|
16
|
+
}
|
package/dist/tools.js
CHANGED
|
@@ -196,6 +196,8 @@ const EVENT_SUMMARY_FIELDS = [
|
|
|
196
196
|
"startDate",
|
|
197
197
|
"endDate",
|
|
198
198
|
"resolvedAt",
|
|
199
|
+
"resolvedAtBasis",
|
|
200
|
+
"settlementWindowClosedAt",
|
|
199
201
|
"freshness",
|
|
200
202
|
"volume",
|
|
201
203
|
"volume24h",
|
|
@@ -205,6 +207,8 @@ const EVENT_SUMMARY_FIELDS = [
|
|
|
205
207
|
"bestBid",
|
|
206
208
|
"bestAsk",
|
|
207
209
|
"spread",
|
|
210
|
+
"spreadPoints",
|
|
211
|
+
"probabilityBook",
|
|
208
212
|
"marketsCount",
|
|
209
213
|
"referenceProbability",
|
|
210
214
|
"crossPlatform",
|
|
@@ -215,6 +219,7 @@ const OUTCOME_SUMMARY_FIELDS = [
|
|
|
215
219
|
"externalMarketId",
|
|
216
220
|
"name",
|
|
217
221
|
"probability",
|
|
222
|
+
"normalizedProbability",
|
|
218
223
|
"priceChange24h",
|
|
219
224
|
];
|
|
220
225
|
const WHALE_TRADE_FIELDS = [
|
|
@@ -253,7 +258,17 @@ function eventSummary(value) {
|
|
|
253
258
|
if (!isJsonRecord(value))
|
|
254
259
|
return value;
|
|
255
260
|
const source = isJsonRecord(value.source)
|
|
256
|
-
? pick(value.source, [
|
|
261
|
+
? pick(value.source, [
|
|
262
|
+
"id",
|
|
263
|
+
"name",
|
|
264
|
+
"kind",
|
|
265
|
+
"supportsTrading",
|
|
266
|
+
// Quotes remain venue-native. Magnitude cannot distinguish a 0.5-point
|
|
267
|
+
// bid from a 0.5 ratio; keep the API's explicit unit with the number.
|
|
268
|
+
"quoteScale",
|
|
269
|
+
"methodology",
|
|
270
|
+
"supportsMarketMetrics",
|
|
271
|
+
])
|
|
257
272
|
: value.source;
|
|
258
273
|
const outcomes = Array.isArray(value.outcomes)
|
|
259
274
|
? value.outcomes
|
|
@@ -799,6 +814,9 @@ export function registerTools(server, client) {
|
|
|
799
814
|
"1H=60x1-minute, 1D=288x5-minute, 1W=672x15-minute, 1M=720x1-hour, " +
|
|
800
815
|
"3M=540x4-hour candles. Candles are oldest to newest with t in unix " +
|
|
801
816
|
"SECONDS; o/h/l/c in fiat (default USD), v always in USD. " +
|
|
817
|
+
"These are sampled composite-price bars, not venue trade candles. " +
|
|
818
|
+
"v is the mean rolling 24-hour quote-volume observation in the bar, " +
|
|
819
|
+
"NOT volume traded during that candle; do not sum v across bars. " +
|
|
802
820
|
PAPER_NOTE,
|
|
803
821
|
inputSchema: {
|
|
804
822
|
coinId: z
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coinrithm/mcp-trading",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.10",
|
|
4
4
|
"mcpName": "io.github.CoinRithm/mcp-trading",
|
|
5
5
|
"description": "CoinRithm paper-trading toolkit: an MCP server (coinrithm-mcp) AND a self-host agent runner (coinrithm-agent) for spot, futures, and prediction markets with a user-minted API key.",
|
|
6
6
|
"type": "module",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"build": "tsc -p tsconfig.json",
|
|
32
32
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
33
33
|
"test": "vitest run",
|
|
34
|
+
"test:coverage": "vitest run --coverage",
|
|
34
35
|
"lint": "eslint src",
|
|
35
36
|
"lint:fix": "eslint src --fix",
|
|
36
37
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
@@ -77,10 +78,20 @@
|
|
|
77
78
|
"@eslint/js": "^9.13.0",
|
|
78
79
|
"@types/express": "^4.17.21",
|
|
79
80
|
"@types/node": "^20.14.0",
|
|
81
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
80
82
|
"eslint": "^9.13.0",
|
|
81
83
|
"prettier": "^3.3.3",
|
|
82
84
|
"typescript": "^5.5.0",
|
|
83
85
|
"typescript-eslint": "^8.11.0",
|
|
84
|
-
"vitest": "
|
|
86
|
+
"vitest": "4.1.11"
|
|
87
|
+
},
|
|
88
|
+
"exports": {
|
|
89
|
+
".": "./dist/index.js",
|
|
90
|
+
"./engine": {
|
|
91
|
+
"types": "./dist/agent/engine.d.ts",
|
|
92
|
+
"import": "./dist/agent/engine.js"
|
|
93
|
+
},
|
|
94
|
+
"./package.json": "./package.json",
|
|
95
|
+
"./dist/*": "./dist/*"
|
|
85
96
|
}
|
|
86
97
|
}
|