@aihubspot/agent-trade-mcp 0.1.0 → 0.1.1
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/README.md +15 -0
- package/dist/index.js +367 -61
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -3,3 +3,18 @@
|
|
|
3
3
|
Install with `npm install -g @aihubspot/agent-trade-mcp`, then start `ai-hub-trade-mcp --profile default`.
|
|
4
4
|
|
|
5
5
|
This package provides a local stdio MCP server. State-changing operations require a preview and a new explicit user confirmation.
|
|
6
|
+
|
|
7
|
+
It reads the same local profile as the CLI from `~/.ai-hub/config.toml`. The API key and secret key are stored as plaintext in that file with mode `600`; configure them once through `ai-hub config set-credentials --profile <name>`. Do not share this file.
|
|
8
|
+
|
|
9
|
+
## Client setup
|
|
10
|
+
|
|
11
|
+
Register the local stdio MCP server with one supported client. The setup command is the only client-configuration entry point in this phase:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
ai-hub-trade-mcp setup --client cursor --profile default
|
|
15
|
+
ai-hub-trade-mcp setup --client claude-desktop --profile default
|
|
16
|
+
ai-hub-trade-mcp setup --client claude-code --profile default
|
|
17
|
+
ai-hub-trade-mcp setup --client codex --profile default
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Setup registers the currently installed MCP binary through its absolute Node runtime and entrypoint path, so desktop clients do not depend on a global PATH or an unpublished `npx` package. Cursor and Claude Desktop configurations are merged with existing MCP servers through their JSON configurations. Claude Code and Codex are registered through their official CLIs; Claude Code uses user scope. Existing JSON configurations are validated, atomically updated, and backed up before their first modification. The setup command stores no API credentials; the MCP server continues to read its profile from `~/.ai-hub/config.toml`.
|
package/dist/index.js
CHANGED
|
@@ -12,12 +12,30 @@ var AiHubError = class extends Error {
|
|
|
12
12
|
this.code = code;
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
|
+
var OpenApiBusinessError = class extends AiHubError {
|
|
16
|
+
constructor(diagnosis) {
|
|
17
|
+
super(
|
|
18
|
+
"AI_HUB_OPENAPI_BUSINESS_ERROR",
|
|
19
|
+
`OpenAPI business error ${diagnosis.upstreamCode}: ${diagnosis.upstreamMessage}`
|
|
20
|
+
);
|
|
21
|
+
this.diagnosis = diagnosis;
|
|
22
|
+
this.name = "OpenApiBusinessError";
|
|
23
|
+
}
|
|
24
|
+
diagnosis;
|
|
25
|
+
};
|
|
26
|
+
function toAiHubErrorPayload(error) {
|
|
27
|
+
if (error instanceof OpenApiBusinessError) {
|
|
28
|
+
return { code: error.code, ...error.diagnosis };
|
|
29
|
+
}
|
|
30
|
+
if (error instanceof AiHubError) {
|
|
31
|
+
return { code: error.code, message: error.message };
|
|
32
|
+
}
|
|
33
|
+
return { code: "AI_HUB_UNEXPECTED_ERROR", message: error instanceof Error ? error.message : "Unexpected error" };
|
|
34
|
+
}
|
|
15
35
|
|
|
16
36
|
// ../core/src/credential.ts
|
|
17
37
|
import { createHash } from "crypto";
|
|
18
|
-
|
|
19
|
-
var SERVICE_NAME = "ai-hub-agent-trade";
|
|
20
|
-
function validate(credentials) {
|
|
38
|
+
function validateApiCredentials(credentials) {
|
|
21
39
|
const apiKey = credentials.apiKey.trim();
|
|
22
40
|
const secretKey = credentials.secretKey.trim();
|
|
23
41
|
if (!apiKey || !secretKey) {
|
|
@@ -28,28 +46,11 @@ function validate(credentials) {
|
|
|
28
46
|
function versionOf(credentials) {
|
|
29
47
|
return createHash("sha256").update(`${credentials.apiKey}\0${credentials.secretKey}`).digest("hex");
|
|
30
48
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
async get(profile) {
|
|
38
|
-
const stored = await keytar.getPassword(SERVICE_NAME, profile);
|
|
39
|
-
if (!stored) return void 0;
|
|
40
|
-
let parsed;
|
|
41
|
-
try {
|
|
42
|
-
parsed = JSON.parse(stored);
|
|
43
|
-
} catch {
|
|
44
|
-
throw new AiHubError("AI_HUB_CREDENTIAL_INVALID", "Stored credentials are unreadable. Set credentials again.");
|
|
45
|
-
}
|
|
46
|
-
const credentials = validate(parsed);
|
|
47
|
-
return { ...credentials, credentialVersion: versionOf(credentials) };
|
|
48
|
-
}
|
|
49
|
-
async remove(profile) {
|
|
50
|
-
return keytar.deletePassword(SERVICE_NAME, profile);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
49
|
+
function loadStoredCredentials(apiKey, secretKey) {
|
|
50
|
+
if (apiKey === void 0 && secretKey === void 0) return void 0;
|
|
51
|
+
const credentials = validateApiCredentials({ apiKey: apiKey ?? "", secretKey: secretKey ?? "" });
|
|
52
|
+
return { ...credentials, credentialVersion: versionOf(credentials) };
|
|
53
|
+
}
|
|
53
54
|
|
|
54
55
|
// ../core/src/confirmation.ts
|
|
55
56
|
import { createHash as createHash2, randomUUID } from "crypto";
|
|
@@ -109,6 +110,100 @@ var ConfirmationService = class {
|
|
|
109
110
|
|
|
110
111
|
// ../core/src/openapi.ts
|
|
111
112
|
import { createHmac } from "crypto";
|
|
113
|
+
|
|
114
|
+
// ../core/src/openapi-error-catalog.ts
|
|
115
|
+
var globalErrors = {
|
|
116
|
+
"-1002": { reason: "API_KEY_REQUIRED", suggestedAction: "Configure credentials for the selected profile, then retry the read request." },
|
|
117
|
+
"-1003": { reason: "RATE_LIMITED", suggestedAction: "Wait before retrying. Reduce request frequency and do not retry a write automatically.", retryable: true },
|
|
118
|
+
"-1006": { reason: "WRITE_STATUS_UNKNOWN", suggestedAction: "Do not retry automatically. Query the affected order or history record before taking another action.", writeOutcomeUnknown: true },
|
|
119
|
+
"-1007": { reason: "UPSTREAM_TIMEOUT", suggestedAction: "For a write, query the affected order or history record before retrying. A read can be retried after a short delay.", retryable: true, writeOutcomeUnknown: true },
|
|
120
|
+
"-1015": { reason: "TOO_MANY_ORDERS", suggestedAction: "Reduce the number of active or requested orders before retrying." },
|
|
121
|
+
"-1020": { reason: "UNSUPPORTED_OPERATION", suggestedAction: "Use a supported command and do not substitute another operation automatically." },
|
|
122
|
+
"-1021": { reason: "INVALID_TIMESTAMP", suggestedAction: "Synchronize the local system clock, then retry the request." },
|
|
123
|
+
"-1022": { reason: "INVALID_SIGNATURE", suggestedAction: "Verify the selected profile credentials and signing configuration, then retry." },
|
|
124
|
+
"-1023": { reason: "TIMESTAMP_REQUIRED", suggestedAction: "Use the AI Hub CLI or MCP client so the request timestamp is supplied automatically." },
|
|
125
|
+
"-1024": { reason: "SIGNATURE_REQUIRED", suggestedAction: "Use the AI Hub CLI or MCP client so the request is signed automatically." },
|
|
126
|
+
"-1100": { reason: "INVALID_REQUEST_CHARACTERS", suggestedAction: "Correct the invalid request value and retry." },
|
|
127
|
+
"-1102": { reason: "MISSING_OR_MALFORMED_PARAMETER", suggestedAction: "Provide every required parameter in the documented format." },
|
|
128
|
+
"-1103": { reason: "UNKNOWN_PARAMETER", suggestedAction: "Remove unsupported parameters and use the CLI command reference." },
|
|
129
|
+
"-1111": { reason: "INVALID_PRECISION", suggestedAction: "Use the symbol's allowed price and quantity precision." },
|
|
130
|
+
"-1121": { reason: "INVALID_SYMBOL", suggestedAction: "Query supported symbols and use an exact symbol value." },
|
|
131
|
+
"-1136": { reason: "ORDER_QUANTITY_TOO_SMALL", suggestedAction: "Increase the order quantity to the symbol minimum." },
|
|
132
|
+
"-1138": { reason: "ORDER_PRICE_OUT_OF_RANGE", suggestedAction: "Use a price inside the allowed range, then create a new preview." },
|
|
133
|
+
"-1139": { reason: "MARKET_ORDER_UNSUPPORTED", suggestedAction: "Use a supported order type for the selected symbol." },
|
|
134
|
+
"-1145": { reason: "ORDER_CANNOT_BE_CANCELLED", suggestedAction: "Query the latest order status before attempting another cancellation." },
|
|
135
|
+
"-2013": { reason: "ORDER_NOT_FOUND", suggestedAction: "Verify the symbol and order ID, then query open orders if needed." },
|
|
136
|
+
"-2014": { reason: "INVALID_API_KEY_FORMAT", suggestedAction: "Replace the configured API key with a valid value." },
|
|
137
|
+
"-2015": { reason: "API_KEY_REJECTED", suggestedAction: "Verify API key permissions, IP restrictions, and the selected tenant profile." },
|
|
138
|
+
"-2016": { reason: "TRADING_DISABLED", suggestedAction: "The account is restricted. Contact the platform administrator before trying again." },
|
|
139
|
+
"-2017": { reason: "INSUFFICIENT_BALANCE", suggestedAction: "Check the available balance and reduce the requested amount before creating a new preview." },
|
|
140
|
+
"-2018": { reason: "DUPLICATE_WITHDRAWAL_REQUEST", suggestedAction: "Use a new withdrawal order ID or query withdrawal history before retrying." },
|
|
141
|
+
"-2021": { reason: "IDENTITY_VERIFICATION_REQUIRED", suggestedAction: "Complete the account verification required by the tenant before retrying." },
|
|
142
|
+
"-2022": { reason: "LIMIT_ORDER_VOLUME_TOO_SMALL", suggestedAction: "Increase the limit-order volume to the returned minimum." },
|
|
143
|
+
"-2023": { reason: "MARKET_ORDER_VOLUME_TOO_SMALL", suggestedAction: "Increase the market-order sell volume to the returned minimum." },
|
|
144
|
+
"-2024": { reason: "LIMIT_ORDER_PRICE_TOO_SMALL", suggestedAction: "Increase the limit-order price to the returned minimum." },
|
|
145
|
+
"-2025": { reason: "MARKET_ORDER_AMOUNT_TOO_SMALL", suggestedAction: "Increase the market-order buy amount to the returned minimum." },
|
|
146
|
+
"-2026": { reason: "BUY_AMOUNT_TOO_LARGE", suggestedAction: "Reduce the buy amount before creating a new preview." },
|
|
147
|
+
"-2027": { reason: "SELL_AMOUNT_TOO_LARGE", suggestedAction: "Reduce the sell amount before creating a new preview." },
|
|
148
|
+
"-2029": { reason: "LIMIT_ORDER_NOTIONAL_TOO_SMALL", suggestedAction: "Increase the limit-order notional amount to the returned minimum." },
|
|
149
|
+
"-2030": { reason: "TRANSFER_NOT_FOUND", suggestedAction: "Verify the transfer ID, or query by the documented source and destination accounts." },
|
|
150
|
+
"-2031": { reason: "TRANSFER_TYPE_UNSUPPORTED", suggestedAction: "Use a supported source and destination account combination." },
|
|
151
|
+
"-2033": { reason: "ASSET_TRANSFER_FAILED", suggestedAction: "Query transfer history before attempting another transfer." },
|
|
152
|
+
"-2034": { reason: "SUB_ACCOUNT_RELATION_NOT_FOUND", suggestedAction: "Use a sub-account that belongs to the authenticated parent account." },
|
|
153
|
+
"-2035": { reason: "SUB_ACCOUNT_API_KEY_NOT_FOUND", suggestedAction: "Verify the sub-account API key identifier before retrying." },
|
|
154
|
+
"-2036": { reason: "SUB_ACCOUNT_PERMISSION_DENIED", suggestedAction: "Use a parent-account credential with the required sub-account permission." },
|
|
155
|
+
"-2037": { reason: "SUB_ACCOUNT_CREATION_FAILED", suggestedAction: "Check the sub-account limit and requested identifier before creating a new preview." },
|
|
156
|
+
"-2038": { reason: "SUB_ACCOUNT_UPDATE_FAILED", suggestedAction: "Query the latest sub-account state before retrying the update." },
|
|
157
|
+
"-2040": { reason: "SUB_ACCOUNT_CREDENTIAL_REQUIRED", suggestedAction: "Use the sub-account credential required by this endpoint." },
|
|
158
|
+
"10001": { reason: "USER_ACCOUNT_LOCKED", suggestedAction: "The account is restricted. Contact the platform administrator." },
|
|
159
|
+
"10004": { reason: "USER_NOT_AUTHENTICATED", suggestedAction: "Configure valid credentials for the selected profile." },
|
|
160
|
+
"10005": { reason: "PERMISSION_DENIED", suggestedAction: "Verify the API key permissions and the tenant feature permission." },
|
|
161
|
+
"10006": { reason: "TENANT_PERMISSION_DENIED", suggestedAction: "Ask the tenant administrator to enable this capability." },
|
|
162
|
+
"21018": { reason: "SUB_ACCOUNT_NOT_FOUND", suggestedAction: "List sub-accounts and use an existing sub-account ID." },
|
|
163
|
+
"21019": { reason: "COIN_NOT_FOUND", suggestedAction: "Query supported coins and use an exact coin symbol." },
|
|
164
|
+
"21020": { reason: "SYMBOL_NOT_FOUND", suggestedAction: "Query supported symbols and use an exact symbol value." },
|
|
165
|
+
"21022": { reason: "ORDER_NOT_FOUND", suggestedAction: "Verify the order identifier and symbol, then query current orders if needed." },
|
|
166
|
+
"21024": { reason: "SUB_ACCOUNT_FEATURE_PERMISSION_DENIED", suggestedAction: "Enable the required sub-account feature permission before retrying." },
|
|
167
|
+
"21025": { reason: "SUB_ACCOUNT_LIMIT_REACHED", suggestedAction: "The parent account has reached its sub-account limit." }
|
|
168
|
+
};
|
|
169
|
+
var endpointErrors = {
|
|
170
|
+
"/sapi/v1/withdraw/query:10005": { reason: "WITHDRAW_HISTORY_PERMISSION_DENIED", suggestedAction: "Enable the withdrawal-history permission for this API key, then retry." },
|
|
171
|
+
"/sapi/v1/withdraw/query:-2015": { reason: "WITHDRAW_HISTORY_TRUSTED_IP_REQUIRED", suggestedAction: "Add a trusted IP to the API key before querying withdrawal history." },
|
|
172
|
+
"/sapi/v1/withdraw/query:-1004": { reason: "WITHDRAW_HISTORY_PARENT_ACCOUNT_REQUIRED", suggestedAction: "Use a parent-account credential for withdrawal history." },
|
|
173
|
+
"/sapi/v1/sub_user/asset/root_transfer_query:-2036": { reason: "ROOT_SUB_TRANSFER_PARENT_PERMISSION_REQUIRED", suggestedAction: "Use a parent-account credential that can access the requested sub-account." },
|
|
174
|
+
"/sapi/v1/sub_user/asset/transfer_query:-2034": { reason: "SUB_ACCOUNT_INTERNAL_TRANSFER_RELATION_NOT_FOUND", suggestedAction: "Use a sub-account that belongs to the authenticated parent account." },
|
|
175
|
+
"/sapi/v1/asset/subaccount/transfer_query:-2040": { reason: "PARENT_TRANSFER_SUB_ACCOUNT_REQUIRED", suggestedAction: "Run this query with the relevant sub-account credential." }
|
|
176
|
+
};
|
|
177
|
+
function normalizeCode(value) {
|
|
178
|
+
return String(value).trim();
|
|
179
|
+
}
|
|
180
|
+
function diagnoseOpenApiBusinessError(path2, upstreamCode, upstreamMessage) {
|
|
181
|
+
const code = normalizeCode(upstreamCode);
|
|
182
|
+
const definition = endpointErrors[`${path2}:${code}`] ?? globalErrors[code];
|
|
183
|
+
if (!definition) {
|
|
184
|
+
return {
|
|
185
|
+
upstreamCode: code,
|
|
186
|
+
upstreamMessage,
|
|
187
|
+
reason: "UNKNOWN_UPSTREAM_CODE",
|
|
188
|
+
suggestedAction: "Review the upstream message and endpoint. Do not retry a state-changing request automatically.",
|
|
189
|
+
retryable: false,
|
|
190
|
+
writeOutcomeUnknown: false
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
upstreamCode: code,
|
|
195
|
+
upstreamMessage,
|
|
196
|
+
reason: definition.reason,
|
|
197
|
+
suggestedAction: definition.suggestedAction,
|
|
198
|
+
retryable: definition.retryable ?? false,
|
|
199
|
+
writeOutcomeUnknown: definition.writeOutcomeUnknown ?? false
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function isOpenApiSuccessCode(value) {
|
|
203
|
+
return value === 0 || value === "0";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ../core/src/openapi.ts
|
|
112
207
|
function parseJsonPreservingLargeIntegers(raw) {
|
|
113
208
|
let transformed = "";
|
|
114
209
|
let inString = false;
|
|
@@ -155,13 +250,13 @@ function queryString(values) {
|
|
|
155
250
|
}
|
|
156
251
|
return params.toString();
|
|
157
252
|
}
|
|
158
|
-
function joinUrl(baseUrl,
|
|
159
|
-
if (!
|
|
160
|
-
const url = `${baseUrl.replace(/\/+$/, "")}${
|
|
253
|
+
function joinUrl(baseUrl, path2, query) {
|
|
254
|
+
if (!path2.startsWith("/")) throw new AiHubError("AI_HUB_INVALID_PATH", "OpenAPI paths must start with '/'.");
|
|
255
|
+
const url = `${baseUrl.replace(/\/+$/, "")}${path2}`;
|
|
161
256
|
return query ? `${url}?${query}` : url;
|
|
162
257
|
}
|
|
163
|
-
function signRequest(timestamp, method,
|
|
164
|
-
const payload = `${timestamp}${method.toUpperCase()}${
|
|
258
|
+
function signRequest(timestamp, method, path2, secretKey, query, body) {
|
|
259
|
+
const payload = `${timestamp}${method.toUpperCase()}${path2}${query ? `?${query}` : ""}${body ?? ""}`;
|
|
165
260
|
return createHmac("sha256", secretKey).update(payload).digest("hex");
|
|
166
261
|
}
|
|
167
262
|
var AiHubSpotApi = class {
|
|
@@ -236,13 +331,13 @@ var AiHubSpotApi = class {
|
|
|
236
331
|
* defined by the API document. Keep endpoint selection in the individual Tool
|
|
237
332
|
* definition; never expose this method directly through CLI or MCP.
|
|
238
333
|
*/
|
|
239
|
-
signedGet(
|
|
240
|
-
return this.request("GET",
|
|
334
|
+
signedGet(path2, query, credentials) {
|
|
335
|
+
return this.request("GET", path2, query, void 0, credentials);
|
|
241
336
|
}
|
|
242
|
-
signedPost(
|
|
243
|
-
return this.request("POST",
|
|
337
|
+
signedPost(path2, body, credentials) {
|
|
338
|
+
return this.request("POST", path2, {}, body, credentials);
|
|
244
339
|
}
|
|
245
|
-
async request(method,
|
|
340
|
+
async request(method, path2, query = {}, body, credentials) {
|
|
246
341
|
const encodedQuery = queryString(query);
|
|
247
342
|
const bodyString = body ? JSON.stringify(body) : void 0;
|
|
248
343
|
const headers = {
|
|
@@ -255,11 +350,11 @@ var AiHubSpotApi = class {
|
|
|
255
350
|
const timestamp = Date.now().toString();
|
|
256
351
|
headers["X-CH-APIKEY"] = credentials.apiKey;
|
|
257
352
|
headers["X-CH-TS"] = timestamp;
|
|
258
|
-
headers["X-CH-SIGN"] = signRequest(timestamp, method,
|
|
353
|
+
headers["X-CH-SIGN"] = signRequest(timestamp, method, path2, credentials.secretKey, encodedQuery || void 0, bodyString);
|
|
259
354
|
}
|
|
260
355
|
let response;
|
|
261
356
|
try {
|
|
262
|
-
response = await fetch(joinUrl(this.baseUrl,
|
|
357
|
+
response = await fetch(joinUrl(this.baseUrl, path2, encodedQuery), {
|
|
263
358
|
method,
|
|
264
359
|
headers,
|
|
265
360
|
body: bodyString,
|
|
@@ -277,8 +372,18 @@ var AiHubSpotApi = class {
|
|
|
277
372
|
throw new AiHubError("AI_HUB_OPENAPI_HTTP_ERROR", `OpenAPI returned HTTP ${response.status}.`);
|
|
278
373
|
}
|
|
279
374
|
try {
|
|
280
|
-
|
|
281
|
-
|
|
375
|
+
const parsed = parseJsonPreservingLargeIntegers(raw);
|
|
376
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
377
|
+
const payload = parsed;
|
|
378
|
+
if (Object.hasOwn(payload, "code") && !isOpenApiSuccessCode(payload.code)) {
|
|
379
|
+
const upstreamCode = typeof payload.code === "string" || typeof payload.code === "number" ? payload.code : String(payload.code);
|
|
380
|
+
const upstreamMessage = typeof payload.msg === "string" ? payload.msg : "OpenAPI returned a business failure without a message.";
|
|
381
|
+
throw new OpenApiBusinessError(diagnoseOpenApiBusinessError(path2, upstreamCode, upstreamMessage));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return parsed;
|
|
385
|
+
} catch (error) {
|
|
386
|
+
if (error instanceof OpenApiBusinessError) throw error;
|
|
282
387
|
throw new AiHubError("AI_HUB_OPENAPI_INVALID_RESPONSE", "OpenAPI returned a non-JSON response.");
|
|
283
388
|
}
|
|
284
389
|
}
|
|
@@ -377,7 +482,11 @@ var ConfigStore = class {
|
|
|
377
482
|
const config = await this.read();
|
|
378
483
|
const normalizedUrl = await normalizeOpenApiBaseUrl(openApiBaseUrl);
|
|
379
484
|
const existing = config.profiles[name];
|
|
380
|
-
config.profiles[name] = {
|
|
485
|
+
config.profiles[name] = {
|
|
486
|
+
openapi_base_url: normalizedUrl,
|
|
487
|
+
...existing?.api_key ? { api_key: existing.api_key } : {},
|
|
488
|
+
...existing?.secret_key ? { secret_key: existing.secret_key } : {}
|
|
489
|
+
};
|
|
381
490
|
if (Object.keys(config.profiles).length === 1) config.default_profile = name;
|
|
382
491
|
await this.write(config);
|
|
383
492
|
return this.resolveFrom(config, name);
|
|
@@ -386,6 +495,12 @@ var ConfigStore = class {
|
|
|
386
495
|
const config = await this.read();
|
|
387
496
|
return this.resolveFrom(config, requestedName ?? config.default_profile);
|
|
388
497
|
}
|
|
498
|
+
async getCredentials(requestedName) {
|
|
499
|
+
const config = await this.read();
|
|
500
|
+
const name = requestedName ?? config.default_profile;
|
|
501
|
+
const profile = this.profileFrom(config, name);
|
|
502
|
+
return loadStoredCredentials(profile.api_key, profile.secret_key);
|
|
503
|
+
}
|
|
389
504
|
async removeProfile(name) {
|
|
390
505
|
validateProfileName(name);
|
|
391
506
|
const config = await this.read();
|
|
@@ -394,26 +509,31 @@ var ConfigStore = class {
|
|
|
394
509
|
if (config.default_profile === name) config.default_profile = Object.keys(config.profiles)[0] ?? "default";
|
|
395
510
|
await this.write(config);
|
|
396
511
|
}
|
|
397
|
-
async
|
|
512
|
+
async setCredentials(name, credentials) {
|
|
398
513
|
validateProfileName(name);
|
|
399
514
|
const config = await this.read();
|
|
400
515
|
const profile = config.profiles[name];
|
|
401
516
|
if (!profile) throw new AiHubError("AI_HUB_PROFILE_NOT_FOUND", `Profile "${name}" does not exist.`);
|
|
402
|
-
|
|
517
|
+
const value = validateApiCredentials(credentials);
|
|
518
|
+
config.profiles[name] = { openapi_base_url: profile.openapi_base_url, api_key: value.apiKey, secret_key: value.secretKey };
|
|
403
519
|
await this.write(config);
|
|
404
520
|
return this.resolveFrom(config, name);
|
|
405
521
|
}
|
|
406
522
|
resolveFrom(config, name) {
|
|
407
523
|
validateProfileName(name);
|
|
408
|
-
const profile = config
|
|
409
|
-
if (!profile) throw new AiHubError("AI_HUB_PROFILE_NOT_FOUND", `Profile "${name}" does not exist.`);
|
|
524
|
+
const profile = this.profileFrom(config, name);
|
|
410
525
|
return {
|
|
411
526
|
name,
|
|
412
527
|
openApiBaseUrl: profile.openapi_base_url,
|
|
413
|
-
credentialRef: profile.credential_ref,
|
|
414
528
|
configVersion: versionOf2(config, name)
|
|
415
529
|
};
|
|
416
530
|
}
|
|
531
|
+
profileFrom(config, name) {
|
|
532
|
+
validateProfileName(name);
|
|
533
|
+
const profile = config.profiles[name];
|
|
534
|
+
if (!profile) throw new AiHubError("AI_HUB_PROFILE_NOT_FOUND", `Profile "${name}" does not exist.`);
|
|
535
|
+
return profile;
|
|
536
|
+
}
|
|
417
537
|
async write(config) {
|
|
418
538
|
const directory = dirname(this.filePath);
|
|
419
539
|
await mkdir(directory, { recursive: true, mode: 448 });
|
|
@@ -428,8 +548,9 @@ var ConfigStore = class {
|
|
|
428
548
|
|
|
429
549
|
// ../core/src/tools/execution-context.ts
|
|
430
550
|
async function createToolExecutionContext(profileName) {
|
|
431
|
-
const
|
|
432
|
-
const
|
|
551
|
+
const store = new ConfigStore();
|
|
552
|
+
const profile = await store.showProfile(profileName);
|
|
553
|
+
const credentials = await store.getCredentials(profile.name);
|
|
433
554
|
return { profile, credentials, api: new AiHubSpotApi(profile.openApiBaseUrl) };
|
|
434
555
|
}
|
|
435
556
|
function confirmationContext(context) {
|
|
@@ -485,7 +606,7 @@ var accountTools = [
|
|
|
485
606
|
operation: "read",
|
|
486
607
|
riskLevel: "low",
|
|
487
608
|
inputSchema: { type: "object", additionalProperties: false },
|
|
488
|
-
errorCodes: ["AI_HUB_CREDENTIAL_NOT_CONFIGURED", "AI_HUB_OPENAPI_NETWORK_ERROR", "AI_HUB_OPENAPI_HTTP_ERROR", "AI_HUB_OPENAPI_INVALID_RESPONSE"],
|
|
609
|
+
errorCodes: ["AI_HUB_CREDENTIAL_NOT_CONFIGURED", "AI_HUB_OPENAPI_NETWORK_ERROR", "AI_HUB_OPENAPI_HTTP_ERROR", "AI_HUB_OPENAPI_INVALID_RESPONSE", "AI_HUB_OPENAPI_BUSINESS_ERROR"],
|
|
489
610
|
validate: (input) => strictObject(input, []),
|
|
490
611
|
handler: async (_input, context) => {
|
|
491
612
|
if (!context.credentials) throw new AiHubError("AI_HUB_CREDENTIAL_NOT_CONFIGURED", `Credentials are not configured for profile "${context.profile.name}".`);
|
|
@@ -496,7 +617,7 @@ var accountTools = [
|
|
|
496
617
|
|
|
497
618
|
// ../core/src/tools/tool-utils.ts
|
|
498
619
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
499
|
-
var signedReadErrors = ["AI_HUB_INVALID_ARGUMENT", "AI_HUB_CREDENTIAL_NOT_CONFIGURED", "AI_HUB_OPENAPI_NETWORK_ERROR", "AI_HUB_OPENAPI_HTTP_ERROR", "AI_HUB_OPENAPI_INVALID_RESPONSE"];
|
|
620
|
+
var signedReadErrors = ["AI_HUB_INVALID_ARGUMENT", "AI_HUB_CREDENTIAL_NOT_CONFIGURED", "AI_HUB_OPENAPI_NETWORK_ERROR", "AI_HUB_OPENAPI_HTTP_ERROR", "AI_HUB_OPENAPI_INVALID_RESPONSE", "AI_HUB_OPENAPI_BUSINESS_ERROR"];
|
|
500
621
|
var writeErrors = [...signedReadErrors, "AI_HUB_WRITE_CONFIRMATION_REQUIRED", "AI_HUB_CONFIRMATION_REQUIRED", "AI_HUB_CONFIRMATION_EXPIRED", "AI_HUB_CONFIRMATION_CONTEXT_CHANGED", "AI_HUB_CONFIRMATION_NOT_FOUND"];
|
|
501
622
|
function signed(context) {
|
|
502
623
|
if (!context.credentials) throw new AiHubError("AI_HUB_CREDENTIAL_NOT_CONFIGURED", `Credentials are not configured for profile "${context.profile.name}".`);
|
|
@@ -744,7 +865,7 @@ var assetTools = [
|
|
|
744
865
|
];
|
|
745
866
|
|
|
746
867
|
// ../core/src/tools/market-tools.ts
|
|
747
|
-
var readErrors = ["AI_HUB_INVALID_ARGUMENT", "AI_HUB_OPENAPI_NETWORK_ERROR", "AI_HUB_OPENAPI_HTTP_ERROR", "AI_HUB_OPENAPI_INVALID_RESPONSE"];
|
|
868
|
+
var readErrors = ["AI_HUB_INVALID_ARGUMENT", "AI_HUB_OPENAPI_NETWORK_ERROR", "AI_HUB_OPENAPI_HTTP_ERROR", "AI_HUB_OPENAPI_INVALID_RESPONSE", "AI_HUB_OPENAPI_BUSINESS_ERROR"];
|
|
748
869
|
var marketTools = [
|
|
749
870
|
{
|
|
750
871
|
name: "market_ping",
|
|
@@ -756,7 +877,7 @@ var marketTools = [
|
|
|
756
877
|
operation: "read",
|
|
757
878
|
riskLevel: "low",
|
|
758
879
|
inputSchema: { type: "object", additionalProperties: false },
|
|
759
|
-
errorCodes: ["AI_HUB_OPENAPI_NETWORK_ERROR", "AI_HUB_OPENAPI_HTTP_ERROR", "AI_HUB_OPENAPI_INVALID_RESPONSE"],
|
|
880
|
+
errorCodes: ["AI_HUB_OPENAPI_NETWORK_ERROR", "AI_HUB_OPENAPI_HTTP_ERROR", "AI_HUB_OPENAPI_INVALID_RESPONSE", "AI_HUB_OPENAPI_BUSINESS_ERROR"],
|
|
760
881
|
validate: (input) => {
|
|
761
882
|
strictObject(input, []);
|
|
762
883
|
return {};
|
|
@@ -973,7 +1094,7 @@ var marginTools = [
|
|
|
973
1094
|
|
|
974
1095
|
// ../core/src/tools/order-tools.ts
|
|
975
1096
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
976
|
-
var signedReadErrors2 = ["AI_HUB_INVALID_ARGUMENT", "AI_HUB_CREDENTIAL_NOT_CONFIGURED", "AI_HUB_OPENAPI_NETWORK_ERROR", "AI_HUB_OPENAPI_HTTP_ERROR", "AI_HUB_OPENAPI_INVALID_RESPONSE"];
|
|
1097
|
+
var signedReadErrors2 = ["AI_HUB_INVALID_ARGUMENT", "AI_HUB_CREDENTIAL_NOT_CONFIGURED", "AI_HUB_OPENAPI_NETWORK_ERROR", "AI_HUB_OPENAPI_HTTP_ERROR", "AI_HUB_OPENAPI_INVALID_RESPONSE", "AI_HUB_OPENAPI_BUSINESS_ERROR"];
|
|
977
1098
|
var writeErrors2 = [...signedReadErrors2, "AI_HUB_WRITE_CONFIRMATION_REQUIRED", "AI_HUB_CONFIRMATION_REQUIRED", "AI_HUB_CONFIRMATION_EXPIRED", "AI_HUB_CONFIRMATION_CONTEXT_CHANGED", "AI_HUB_CONFIRMATION_NOT_FOUND"];
|
|
978
1099
|
var decimal = /^(?:0|[1-9]\d*)(?:\.\d+)?$/;
|
|
979
1100
|
function positiveDecimal2(value, name) {
|
|
@@ -1446,12 +1567,12 @@ var ToolRegistry = class {
|
|
|
1446
1567
|
toolsByCliPath = /* @__PURE__ */ new Map();
|
|
1447
1568
|
constructor(tools = allTools) {
|
|
1448
1569
|
for (const tool of tools) {
|
|
1449
|
-
const
|
|
1450
|
-
if (this.toolsByName.has(tool.name) || this.toolsByCliPath.has(
|
|
1570
|
+
const path2 = tool.cliPath.join(" ");
|
|
1571
|
+
if (this.toolsByName.has(tool.name) || this.toolsByCliPath.has(path2)) {
|
|
1451
1572
|
throw new AiHubError("AI_HUB_TOOL_DUPLICATE", `Duplicate tool registration: ${tool.name}.`);
|
|
1452
1573
|
}
|
|
1453
1574
|
this.toolsByName.set(tool.name, tool);
|
|
1454
|
-
this.toolsByCliPath.set(
|
|
1575
|
+
this.toolsByCliPath.set(path2, tool);
|
|
1455
1576
|
}
|
|
1456
1577
|
}
|
|
1457
1578
|
list(options = {}) {
|
|
@@ -1464,10 +1585,10 @@ var ToolRegistry = class {
|
|
|
1464
1585
|
}
|
|
1465
1586
|
return tool;
|
|
1466
1587
|
}
|
|
1467
|
-
byCliPath(
|
|
1468
|
-
const tool = this.toolsByCliPath.get(
|
|
1588
|
+
byCliPath(path2, options = {}) {
|
|
1589
|
+
const tool = this.toolsByCliPath.get(path2.join(" "));
|
|
1469
1590
|
if (!tool || options.readOnly && tool.operation !== "read") {
|
|
1470
|
-
throw new AiHubError("AI_HUB_TOOL_NOT_AVAILABLE", `Command "${
|
|
1591
|
+
throw new AiHubError("AI_HUB_TOOL_NOT_AVAILABLE", `Command "${path2.join(" ")}" is not available.`);
|
|
1471
1592
|
}
|
|
1472
1593
|
return tool;
|
|
1473
1594
|
}
|
|
@@ -1532,6 +1653,176 @@ var ToolWriteExecutor = class {
|
|
|
1532
1653
|
}
|
|
1533
1654
|
};
|
|
1534
1655
|
|
|
1656
|
+
// ../core/src/setup.ts
|
|
1657
|
+
import { execFileSync } from "child_process";
|
|
1658
|
+
import * as fs from "fs";
|
|
1659
|
+
import * as os from "os";
|
|
1660
|
+
import * as path from "path";
|
|
1661
|
+
var MCP_BINARY = "ai-hub-trade-mcp";
|
|
1662
|
+
var MCP_CLIENT_NAMES = {
|
|
1663
|
+
cursor: "Cursor",
|
|
1664
|
+
"claude-desktop": "Claude Desktop",
|
|
1665
|
+
"claude-code": "Claude Code",
|
|
1666
|
+
codex: "Codex"
|
|
1667
|
+
};
|
|
1668
|
+
var SUPPORTED_MCP_CLIENTS = Object.keys(MCP_CLIENT_NAMES);
|
|
1669
|
+
function runtime() {
|
|
1670
|
+
const home = os.homedir();
|
|
1671
|
+
return {
|
|
1672
|
+
home,
|
|
1673
|
+
platform: process.platform,
|
|
1674
|
+
cwd: process.cwd(),
|
|
1675
|
+
appData: process.env.APPDATA,
|
|
1676
|
+
localAppData: process.env.LOCALAPPDATA,
|
|
1677
|
+
xdgConfigHome: process.env.XDG_CONFIG_HOME
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
function windowsAppData(value, home) {
|
|
1681
|
+
return value ?? path.join(home, "AppData", "Roaming");
|
|
1682
|
+
}
|
|
1683
|
+
function findMicrosoftStoreClaudePath(value, home) {
|
|
1684
|
+
const packagesDirectory = path.join(value ?? path.join(home, "AppData", "Local"), "Packages");
|
|
1685
|
+
try {
|
|
1686
|
+
const packageName = fs.readdirSync(packagesDirectory).find((entry) => entry.startsWith("Claude_"));
|
|
1687
|
+
if (!packageName) return void 0;
|
|
1688
|
+
const configPath = path.join(
|
|
1689
|
+
packagesDirectory,
|
|
1690
|
+
packageName,
|
|
1691
|
+
"LocalCache",
|
|
1692
|
+
"Roaming",
|
|
1693
|
+
"Claude",
|
|
1694
|
+
"claude_desktop_config.json"
|
|
1695
|
+
);
|
|
1696
|
+
return fs.existsSync(configPath) || fs.existsSync(path.dirname(configPath)) ? configPath : void 0;
|
|
1697
|
+
} catch {
|
|
1698
|
+
return void 0;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
function getMcpClientConfigPath(client, value = runtime()) {
|
|
1702
|
+
if (client === "cursor") return path.join(value.home, ".cursor", "mcp.json");
|
|
1703
|
+
if (value.platform === "win32") {
|
|
1704
|
+
return findMicrosoftStoreClaudePath(value.localAppData, value.home) ?? path.join(windowsAppData(value.appData, value.home), "Claude", "claude_desktop_config.json");
|
|
1705
|
+
}
|
|
1706
|
+
if (value.platform === "darwin") {
|
|
1707
|
+
return path.join(value.home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
1708
|
+
}
|
|
1709
|
+
return path.join(value.xdgConfigHome ?? path.join(value.home, ".config"), "Claude", "claude_desktop_config.json");
|
|
1710
|
+
}
|
|
1711
|
+
function buildServerSpec(profile, launch) {
|
|
1712
|
+
return {
|
|
1713
|
+
name: serverName(profile),
|
|
1714
|
+
command: launch.command,
|
|
1715
|
+
args: [...launch.args, ...profile ? ["--profile", profile] : []]
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
function serverName(profile) {
|
|
1719
|
+
return profile ? `${MCP_BINARY}-${profile}` : MCP_BINARY;
|
|
1720
|
+
}
|
|
1721
|
+
function isRecord(value) {
|
|
1722
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1723
|
+
}
|
|
1724
|
+
function mergeJsonMcpConfig(configPath, server) {
|
|
1725
|
+
const directory = path.dirname(configPath);
|
|
1726
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
1727
|
+
const configExists = fs.existsSync(configPath);
|
|
1728
|
+
let config = {};
|
|
1729
|
+
if (configExists) {
|
|
1730
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
1731
|
+
try {
|
|
1732
|
+
const parsed = JSON.parse(raw);
|
|
1733
|
+
if (!isRecord(parsed)) throw new Error("root must be a JSON object");
|
|
1734
|
+
config = parsed;
|
|
1735
|
+
} catch {
|
|
1736
|
+
throw new Error(`Failed to parse existing MCP configuration at ${configPath}. No changes were made.`);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
if (config.mcpServers === void 0) {
|
|
1740
|
+
config.mcpServers = {};
|
|
1741
|
+
}
|
|
1742
|
+
if (!isRecord(config.mcpServers)) {
|
|
1743
|
+
throw new Error(`Existing MCP configuration at ${configPath} has an invalid mcpServers object. No changes were made.`);
|
|
1744
|
+
}
|
|
1745
|
+
if (configExists) {
|
|
1746
|
+
const backupPath = `${configPath}.bak`;
|
|
1747
|
+
if (!fs.existsSync(backupPath)) {
|
|
1748
|
+
fs.copyFileSync(configPath, backupPath);
|
|
1749
|
+
process.stdout.write(`Backup created: ${backupPath}
|
|
1750
|
+
`);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
config.mcpServers[server.name] = { command: server.command, args: server.args };
|
|
1754
|
+
const mode = configExists ? fs.statSync(configPath).mode & 511 : 384;
|
|
1755
|
+
const temporaryPath = `${configPath}.${process.pid}.tmp`;
|
|
1756
|
+
fs.writeFileSync(temporaryPath, `${JSON.stringify(config, null, 2)}
|
|
1757
|
+
`, { encoding: "utf8", mode });
|
|
1758
|
+
fs.chmodSync(temporaryPath, mode);
|
|
1759
|
+
fs.renameSync(temporaryPath, configPath);
|
|
1760
|
+
}
|
|
1761
|
+
function executeClientRegistration(command, args, value) {
|
|
1762
|
+
process.stdout.write(`Running: ${command} ${args.join(" ")}
|
|
1763
|
+
`);
|
|
1764
|
+
if (value.executeClientCommand) {
|
|
1765
|
+
value.executeClientCommand(command, args);
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
execFileSync(command, args, { stdio: "inherit" });
|
|
1769
|
+
}
|
|
1770
|
+
function jsonFileAdapter(id) {
|
|
1771
|
+
return {
|
|
1772
|
+
id,
|
|
1773
|
+
name: MCP_CLIENT_NAMES[id],
|
|
1774
|
+
install(server, value) {
|
|
1775
|
+
mergeJsonMcpConfig(getMcpClientConfigPath(id, value), server);
|
|
1776
|
+
process.stdout.write(`Configured ${this.name}: ${getMcpClientConfigPath(id, value)}
|
|
1777
|
+
Restart ${this.name} to apply changes.
|
|
1778
|
+
`);
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
function cliAdapter(id) {
|
|
1783
|
+
const command = id === "claude-code" ? "claude" : "codex";
|
|
1784
|
+
return {
|
|
1785
|
+
id,
|
|
1786
|
+
name: MCP_CLIENT_NAMES[id],
|
|
1787
|
+
install(server, value) {
|
|
1788
|
+
const args = [
|
|
1789
|
+
"mcp",
|
|
1790
|
+
"add",
|
|
1791
|
+
...id === "claude-code" ? ["--scope", "user", "--transport", "stdio"] : [],
|
|
1792
|
+
server.name,
|
|
1793
|
+
"--",
|
|
1794
|
+
server.command,
|
|
1795
|
+
...server.args
|
|
1796
|
+
];
|
|
1797
|
+
executeClientRegistration(command, args, value);
|
|
1798
|
+
process.stdout.write(`Configured ${this.name} with local stdio MCP server "${server.name}".
|
|
1799
|
+
`);
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
var MCP_CLIENT_ADAPTERS = {
|
|
1804
|
+
cursor: jsonFileAdapter("cursor"),
|
|
1805
|
+
"claude-desktop": jsonFileAdapter("claude-desktop"),
|
|
1806
|
+
"claude-code": cliAdapter("claude-code"),
|
|
1807
|
+
codex: cliAdapter("codex")
|
|
1808
|
+
};
|
|
1809
|
+
function runMcpSetup(options, value = runtime()) {
|
|
1810
|
+
const adapter = MCP_CLIENT_ADAPTERS[options.client];
|
|
1811
|
+
if (!adapter) {
|
|
1812
|
+
throw new Error(`Unknown MCP client "${options.client}". Supported clients: ${SUPPORTED_MCP_CLIENTS.join(", ")}.`);
|
|
1813
|
+
}
|
|
1814
|
+
const profile = options.profile ? validateProfileName(options.profile) : void 0;
|
|
1815
|
+
adapter.install(buildServerSpec(profile, options.launch), value);
|
|
1816
|
+
}
|
|
1817
|
+
function printMcpSetupUsage() {
|
|
1818
|
+
process.stdout.write(
|
|
1819
|
+
`Usage: ${MCP_BINARY} setup --client <client> [--profile <name>]
|
|
1820
|
+
|
|
1821
|
+
Supported clients:
|
|
1822
|
+
` + SUPPORTED_MCP_CLIENTS.map((client) => ` ${client.padEnd(16)} ${MCP_CLIENT_NAMES[client]}`).join("\n") + "\n"
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1535
1826
|
// src/server.ts
|
|
1536
1827
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1537
1828
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -1540,8 +1831,8 @@ var CONFIRM_ACTION_TOOL = "confirm_action";
|
|
|
1540
1831
|
function result(data) {
|
|
1541
1832
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1542
1833
|
}
|
|
1543
|
-
function
|
|
1544
|
-
const payload = error
|
|
1834
|
+
function toMcpErrorResult(error) {
|
|
1835
|
+
const payload = toAiHubErrorPayload(error);
|
|
1545
1836
|
return { isError: true, content: [{ type: "text", text: JSON.stringify({ ok: false, ...payload }) }] };
|
|
1546
1837
|
}
|
|
1547
1838
|
function toMcpTool(tool) {
|
|
@@ -1627,7 +1918,7 @@ function createServer(profileName, readOnly) {
|
|
|
1627
1918
|
const data = await registry.execute(request.params.name, request.params.arguments ?? {}, context, { readOnly });
|
|
1628
1919
|
return result({ ok: true, data });
|
|
1629
1920
|
} catch (error) {
|
|
1630
|
-
return
|
|
1921
|
+
return toMcpErrorResult(error);
|
|
1631
1922
|
}
|
|
1632
1923
|
});
|
|
1633
1924
|
return server;
|
|
@@ -1642,6 +1933,21 @@ function readOption(args, option) {
|
|
|
1642
1933
|
return value;
|
|
1643
1934
|
}
|
|
1644
1935
|
async function main(argv) {
|
|
1936
|
+
if (argv[0] === "setup") {
|
|
1937
|
+
const client = readOption(argv.slice(1), "--client");
|
|
1938
|
+
const profile = readOption(argv.slice(1), "--profile");
|
|
1939
|
+
if (!client) {
|
|
1940
|
+
printMcpSetupUsage();
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
if (!SUPPORTED_MCP_CLIENTS.includes(client)) {
|
|
1944
|
+
throw new AiHubError("AI_HUB_INVALID_ARGUMENT", `Unknown MCP client "${client}". Supported clients: ${SUPPORTED_MCP_CLIENTS.join(", ")}.`);
|
|
1945
|
+
}
|
|
1946
|
+
const entrypoint = process.argv[1];
|
|
1947
|
+
if (!entrypoint) throw new AiHubError("AI_HUB_UNEXPECTED_ERROR", "Cannot locate the local MCP server entrypoint for client setup.");
|
|
1948
|
+
runMcpSetup({ client, profile, launch: { command: process.execPath, args: [entrypoint] } });
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1645
1951
|
const profileName = readOption(argv, "--profile");
|
|
1646
1952
|
const readOnly = argv.includes("--read-only");
|
|
1647
1953
|
const server = createServer(profileName, readOnly);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aihubspot/agent-trade-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Local stdio MCP server for AI Hub spot trading tools",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -14,12 +14,11 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsup",
|
|
16
16
|
"typecheck": "tsc --noEmit",
|
|
17
|
-
"test": "tsx --test test/*.test.ts",
|
|
17
|
+
"test": "TSX_TSCONFIG_PATH=./test/tsconfig.json tsx --test test/*.test.ts",
|
|
18
18
|
"clean": "rm -rf dist"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
22
|
-
"keytar": "^7.9.0",
|
|
23
22
|
"smol-toml": "^1.3.4"
|
|
24
23
|
}
|
|
25
24
|
}
|