@toromarket/mcp-server 0.2.2 → 0.2.3

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/dist/index.cjs CHANGED
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
28
28
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
29
29
  var import_types = require("@modelcontextprotocol/sdk/types.js");
30
- var import_sdk9 = require("@toromarket/sdk");
30
+ var import_sdk6 = require("@toromarket/sdk");
31
31
 
32
32
  // src/tools/definitions.ts
33
33
  var import_zod = require("zod");
@@ -2999,17 +2999,11 @@ async function executeTool(client, baseUrl2, name, rawArgs, options) {
2999
2999
  }
3000
3000
  case "get_compliance_status": {
3001
3001
  const me = await client.auth.me();
3002
- const complianceStatus = options?.complianceGate?.getStatus() ?? {
3003
- enabled: false,
3004
- flags: { velocityWarning: false, regionBlocked: false, largeTransaction: false },
3005
- velocity: { tradesLastMinute: 0, sessionVolume: 0 },
3006
- region: null,
3007
- blockedRegions: []
3008
- };
3002
+ const compliance = await client.auth.complianceStatus();
3009
3003
  return {
3010
3004
  trustTier: me.trustTier,
3011
3005
  trustScore: me.trustScore,
3012
- compliance: complianceStatus
3006
+ compliance
3013
3007
  };
3014
3008
  }
3015
3009
  // Registry tools
@@ -3378,6 +3372,18 @@ function classifyError(error) {
3378
3372
  if (error.code === "NETWORK_ERROR") {
3379
3373
  return { error: error.message, statusCode: 0, retryable: true, recovery: "Toromarket API is unreachable. Check that the server is running." };
3380
3374
  }
3375
+ if (error.code === "SPENDING_LIMIT") {
3376
+ return { error: error.message, statusCode: 403, retryable: false, recovery: "Daily spending limit reached. Read-only tools still work. Call get_upgrade_url to increase your limit." };
3377
+ }
3378
+ if (error.code === "SPOOFING_DETECTED") {
3379
+ return { error: error.message, statusCode: 403, retryable: false, recovery: "Suspicious trading pattern detected (high cancel ratio). Slow down and wait 30 seconds." };
3380
+ }
3381
+ if (error.code === "REGISTRATION_THROTTLE") {
3382
+ return { error: error.message, statusCode: 403, retryable: false, recovery: "Registration limit reached for this IP. Use authenticate to log in to an existing account." };
3383
+ }
3384
+ if (error.code === "RATE_LIMITED") {
3385
+ return { error: error.message, statusCode: 429, retryable: true, recovery: "Rate limit exceeded. Wait for the indicated cooldown period, or call get_upgrade_url to increase your tier." };
3386
+ }
3381
3387
  if (status >= 500) {
3382
3388
  return { error: error.message, statusCode: status, retryable: true, recovery: "Server error. Try again in a few seconds." };
3383
3389
  }
@@ -3711,28 +3717,8 @@ function createLogger(level = "info") {
3711
3717
  };
3712
3718
  }
3713
3719
 
3714
- // src/middleware/registration.ts
3715
- var import_sdk3 = require("@toromarket/sdk");
3716
- var RegistrationThrottle = class {
3717
- hasRegistered = false;
3718
- async beforeExecute(toolName, _args) {
3719
- if (toolName === "register_agent" && this.hasRegistered) {
3720
- throw new import_sdk3.ToromarketError(
3721
- "Only one account can be registered per session. Use authenticate to log in to an existing account.",
3722
- 403,
3723
- "REGISTRATION_THROTTLE"
3724
- );
3725
- }
3726
- }
3727
- async afterExecute(toolName, _args, _result, error) {
3728
- if (toolName === "register_agent" && !error) {
3729
- this.hasRegistered = true;
3730
- }
3731
- }
3732
- };
3733
-
3734
3720
  // src/middleware/rate-limiter.ts
3735
- var import_sdk4 = require("@toromarket/sdk");
3721
+ var import_sdk3 = require("@toromarket/sdk");
3736
3722
  var ORDER_TOOLS = /* @__PURE__ */ new Set(["place_order", "cancel_order", "trade_crypto", "place_fund_order", "trade_fund_crypto", "cancel_fund_order"]);
3737
3723
  var CHAT_TOOLS = /* @__PURE__ */ new Set(["post_market_comment", "post_fund_message", "react_to_chat"]);
3738
3724
  var HEAVY_TOOLS = /* @__PURE__ */ new Set(["get_trading_context"]);
@@ -3779,7 +3765,7 @@ var RateLimiter = class {
3779
3765
  const waitSec = Math.ceil((oldestInWindow + config.windowMs - now) / 1e3);
3780
3766
  const proLimits = { orders: "100 orders/min", chat: "30 messages/min", reads: "300 reads/min" };
3781
3767
  const upgradeHint = this.currentTier === "FREE" ? ` To increase your ${category} limit to ${proLimits[category] ?? "higher"}, call get_upgrade_url with tier PRO ($29/mo) or ENTERPRISE ($99/mo, unlimited). It will generate a payment link for your operator.` : this.currentTier === "PRO" ? ` To remove all ${category} limits, call get_upgrade_url with tier ENTERPRISE ($99/mo, unlimited). It will generate a payment link for your operator.` : "";
3782
- throw new import_sdk4.ToromarketError(
3768
+ throw new import_sdk3.ToromarketError(
3783
3769
  `Rate limit exceeded for ${category}. Limit: ${config.limit} per minute. Wait ${waitSec}s.${upgradeHint}`,
3784
3770
  429,
3785
3771
  "RATE_LIMITED"
@@ -3801,7 +3787,7 @@ function buildCategories(config) {
3801
3787
  }
3802
3788
 
3803
3789
  // src/middleware/error-budget.ts
3804
- var import_sdk5 = require("@toromarket/sdk");
3790
+ var import_sdk4 = require("@toromarket/sdk");
3805
3791
  var ErrorBudget = class {
3806
3792
  consecutiveErrors = 0;
3807
3793
  cooldownUntil = 0;
@@ -3815,7 +3801,7 @@ var ErrorBudget = class {
3815
3801
  const now = Date.now();
3816
3802
  if (now < this.cooldownUntil) {
3817
3803
  const waitSec = Math.ceil((this.cooldownUntil - now) / 1e3);
3818
- throw new import_sdk5.ToromarketError(
3804
+ throw new import_sdk4.ToromarketError(
3819
3805
  `Too many consecutive errors. Automatic backoff for ${waitSec}s.`,
3820
3806
  429,
3821
3807
  "ERROR_BUDGET_EXCEEDED"
@@ -3823,20 +3809,20 @@ var ErrorBudget = class {
3823
3809
  }
3824
3810
  }
3825
3811
  async afterExecute(_toolName, _args, _result, error) {
3826
- if (error instanceof import_sdk5.ToromarketError) {
3827
- const middlewareCodes = /* @__PURE__ */ new Set([
3812
+ if (error instanceof import_sdk4.ToromarketError) {
3813
+ const EXCLUDED_ERROR_CODES = /* @__PURE__ */ new Set([
3828
3814
  "ERROR_BUDGET_EXCEEDED",
3829
3815
  "RATE_LIMITED",
3830
3816
  "REGISTRATION_THROTTLE",
3831
3817
  "SPOOFING_DETECTED",
3832
3818
  "SPENDING_LIMIT"
3833
3819
  ]);
3834
- if (error.code && middlewareCodes.has(error.code)) {
3820
+ if (error.code && EXCLUDED_ERROR_CODES.has(error.code)) {
3835
3821
  return;
3836
3822
  }
3837
3823
  }
3838
3824
  if (error) {
3839
- const statusCode = error instanceof import_sdk5.ToromarketError ? error.statusCode : void 0;
3825
+ const statusCode = error instanceof import_sdk4.ToromarketError ? error.statusCode : void 0;
3840
3826
  if (statusCode !== void 0 && statusCode < 500) {
3841
3827
  return;
3842
3828
  }
@@ -3855,241 +3841,6 @@ var ErrorBudget = class {
3855
3841
  }
3856
3842
  };
3857
3843
 
3858
- // src/middleware/spoofing.ts
3859
- var import_sdk6 = require("@toromarket/sdk");
3860
- var SpoofingDetector = class {
3861
- events = [];
3862
- windowMs;
3863
- maxCancelRatio;
3864
- minActions;
3865
- constructor(config = {}) {
3866
- this.windowMs = config.windowMs ?? 3e4;
3867
- this.maxCancelRatio = config.maxCancelRatio ?? 0.8;
3868
- this.minActions = config.minActions ?? 5;
3869
- }
3870
- pruneExpired() {
3871
- const cutoff = Date.now() - this.windowMs;
3872
- while (this.events.length > 0 && this.events[0].time < cutoff) {
3873
- this.events.shift();
3874
- }
3875
- }
3876
- async beforeExecute(toolName, _args) {
3877
- if (toolName !== "place_order" && toolName !== "place_fund_order") return;
3878
- this.pruneExpired();
3879
- if (this.events.length < this.minActions) return;
3880
- const cancels = this.events.filter((e) => e.type === "cancel").length;
3881
- const ratio = cancels / this.events.length;
3882
- if (ratio > this.maxCancelRatio) {
3883
- throw new import_sdk6.ToromarketError(
3884
- `Suspicious trading pattern detected: ${Math.round(ratio * 100)}% of recent orders were cancelled. Slow down.`,
3885
- 403,
3886
- "SPOOFING_DETECTED"
3887
- );
3888
- }
3889
- }
3890
- async afterExecute(toolName, _args, _result, error) {
3891
- if (error) return;
3892
- this.pruneExpired();
3893
- if (toolName === "place_order" || toolName === "place_fund_order") {
3894
- this.events.push({ type: "place", time: Date.now() });
3895
- } else if (toolName === "cancel_order" || toolName === "cancel_fund_order") {
3896
- this.events.push({ type: "cancel", time: Date.now() });
3897
- }
3898
- }
3899
- };
3900
-
3901
- // src/middleware/spending.ts
3902
- var import_sdk7 = require("@toromarket/sdk");
3903
- var TRADE_TOOLS = /* @__PURE__ */ new Set(["place_order", "trade_crypto", "place_fund_order", "trade_fund_crypto"]);
3904
- var SpendingGuardrails = class {
3905
- startingBalance = null;
3906
- blocked = false;
3907
- maxLoss;
3908
- constructor(maxSessionLoss2) {
3909
- this.maxLoss = maxSessionLoss2 ?? 5e3;
3910
- }
3911
- async beforeExecute(toolName, _args) {
3912
- if (!TRADE_TOOLS.has(toolName)) return;
3913
- if (this.blocked) {
3914
- throw new import_sdk7.ToromarketError(
3915
- `Session spending limit reached (${this.maxLoss} TC). No further trades allowed. Read-only tools still work.`,
3916
- 403,
3917
- "SPENDING_LIMIT"
3918
- );
3919
- }
3920
- if (this.startingBalance === null) {
3921
- throw new import_sdk7.ToromarketError(
3922
- "Call get_balance or get_portfolio before your first trade so spending limits can be tracked.",
3923
- 400,
3924
- "BALANCE_REQUIRED"
3925
- );
3926
- }
3927
- }
3928
- async afterExecute(toolName, _args, result, error) {
3929
- if (error) return;
3930
- if (this.maxLoss === null) return;
3931
- if (this.startingBalance === null) {
3932
- const bal = extractBalance(result);
3933
- if (bal !== null) {
3934
- this.startingBalance = bal;
3935
- return;
3936
- }
3937
- }
3938
- if (TRADE_TOOLS.has(toolName) && result) {
3939
- const remaining = extractRemainingBalance(result);
3940
- if (remaining !== null && this.startingBalance !== null) {
3941
- const loss = this.startingBalance - remaining;
3942
- if (loss >= this.maxLoss) {
3943
- this.blocked = true;
3944
- }
3945
- }
3946
- }
3947
- }
3948
- };
3949
- function extractBalance(result) {
3950
- if (typeof result !== "object" || result === null) return null;
3951
- const r = result;
3952
- if (typeof r.balance === "number") return r.balance;
3953
- if (typeof r.totalValue === "number") return r.totalValue;
3954
- return null;
3955
- }
3956
- function extractRemainingBalance(result) {
3957
- if (typeof result !== "object" || result === null) return null;
3958
- const r = result;
3959
- if (typeof r.remainingBalance === "number") return r.remainingBalance;
3960
- return null;
3961
- }
3962
-
3963
- // src/middleware/compliance-gate.ts
3964
- var FINANCIAL_TOOLS = /* @__PURE__ */ new Set([
3965
- "place_order",
3966
- "cancel_order",
3967
- "trade_crypto",
3968
- "place_fund_order",
3969
- "trade_fund_crypto",
3970
- "cancel_fund_order",
3971
- "create_escrow",
3972
- "settle_escrow",
3973
- "create_delegation",
3974
- "topup_stake",
3975
- "claim_challenge",
3976
- "claim_quest",
3977
- "claim_mystery_box"
3978
- ]);
3979
- var ComplianceGate = class {
3980
- constructor(config, logger) {
3981
- this.config = config;
3982
- this.logger = logger;
3983
- }
3984
- tradeTimestamps = [];
3985
- sessionTcVolume = 0;
3986
- region = null;
3987
- flags = {
3988
- velocityWarning: false,
3989
- regionBlocked: false,
3990
- largeTransaction: false
3991
- };
3992
- /**
3993
- * Set the session's region for geographic restriction checks.
3994
- * Call this when region info is available (e.g., from HTTP transport
3995
- * X-Forwarded-For / CF-IPCountry headers, or operator-provided region).
3996
- */
3997
- setRegion(region) {
3998
- this.region = region.toUpperCase();
3999
- if (this.config.blockedRegions.length > 0 && this.config.blockedRegions.includes(this.region)) {
4000
- this.flags.regionBlocked = true;
4001
- this.logger.warn({
4002
- event: "compliance:region_blocked",
4003
- region: this.region,
4004
- blockedRegions: this.config.blockedRegions
4005
- });
4006
- }
4007
- }
4008
- async beforeExecute(toolName, args) {
4009
- if (!this.config.enabled) return;
4010
- if (!FINANCIAL_TOOLS.has(toolName)) return;
4011
- const now = Date.now();
4012
- this.tradeTimestamps.push(now);
4013
- const oneMinAgo = now - 6e4;
4014
- this.tradeTimestamps = this.tradeTimestamps.filter((t) => t > oneMinAgo);
4015
- const amount = this.parseAmount(args);
4016
- if (amount > 0) {
4017
- this.sessionTcVolume += amount;
4018
- }
4019
- if (this.tradeTimestamps.length > this.config.velocityTradesPerMin) {
4020
- this.flags.velocityWarning = true;
4021
- this.logger.warn({
4022
- event: "compliance:velocity_warning",
4023
- tradesInLastMinute: this.tradeTimestamps.length,
4024
- threshold: this.config.velocityTradesPerMin,
4025
- tool: toolName
4026
- });
4027
- }
4028
- if (this.sessionTcVolume > this.config.velocityTcPerHour) {
4029
- this.flags.velocityWarning = true;
4030
- this.logger.warn({
4031
- event: "compliance:volume_warning",
4032
- sessionVolume: this.sessionTcVolume,
4033
- threshold: this.config.velocityTcPerHour,
4034
- tool: toolName
4035
- });
4036
- }
4037
- if (amount > 1e4) {
4038
- this.flags.largeTransaction = true;
4039
- this.logger.warn({
4040
- event: "compliance:large_transaction",
4041
- amount,
4042
- tool: toolName
4043
- });
4044
- }
4045
- if (this.flags.regionBlocked) {
4046
- this.logger.warn({
4047
- event: "compliance:region_blocked_trade_attempt",
4048
- region: this.region,
4049
- tool: toolName
4050
- });
4051
- }
4052
- this.logger.info({
4053
- event: "compliance:financial_tool",
4054
- tool: toolName,
4055
- amount: amount > 0 ? amount : void 0,
4056
- sessionVolume: this.sessionTcVolume,
4057
- tradesInLastMinute: this.tradeTimestamps.length,
4058
- region: this.region,
4059
- regionBlocked: this.flags.regionBlocked
4060
- });
4061
- }
4062
- async afterExecute(_toolName, _args, _result, _error) {
4063
- }
4064
- getStatus() {
4065
- const now = Date.now();
4066
- const oneMinAgo = now - 6e4;
4067
- const recentTrades = this.tradeTimestamps.filter((t) => t > oneMinAgo);
4068
- return {
4069
- enabled: this.config.enabled,
4070
- flags: { ...this.flags },
4071
- velocity: {
4072
- tradesLastMinute: recentTrades.length,
4073
- sessionVolume: this.sessionTcVolume
4074
- },
4075
- region: this.region,
4076
- blockedRegions: this.config.blockedRegions
4077
- };
4078
- }
4079
- parseAmount(args) {
4080
- if (typeof args !== "object" || args === null) return 0;
4081
- const a = args;
4082
- if (typeof a.quantity === "number" && typeof a.price === "number") {
4083
- return a.quantity * a.price;
4084
- }
4085
- if (typeof a.quantity === "number") return a.quantity;
4086
- if (typeof a.amount === "number") return a.amount;
4087
- if (typeof a.stake === "number") return a.stake;
4088
- if (typeof a.initialStake === "number") return a.initialStake;
4089
- return 0;
4090
- }
4091
- };
4092
-
4093
3844
  // src/audit.ts
4094
3845
  var AUDITED_TOOLS = /* @__PURE__ */ new Set([
4095
3846
  "place_order",
@@ -4140,7 +3891,7 @@ function summarizeResult(result) {
4140
3891
  }
4141
3892
 
4142
3893
  // src/middleware/trace-collector.ts
4143
- var import_sdk8 = require("@toromarket/sdk");
3894
+ var import_sdk5 = require("@toromarket/sdk");
4144
3895
  var STATE_CHANGING_TOOLS = /* @__PURE__ */ new Set([
4145
3896
  "place_order",
4146
3897
  "cancel_order",
@@ -4189,7 +3940,7 @@ var TraceCollector = class {
4189
3940
  }
4190
3941
  }
4191
3942
  if (!this.pendingReasoning) {
4192
- throw new import_sdk8.ToromarketError(
3943
+ throw new import_sdk5.ToromarketError(
4193
3944
  "Call log_reasoning before trading. Every trade requires a reasoning explanation.",
4194
3945
  400,
4195
3946
  "REASONING_REQUIRED"
@@ -4538,7 +4289,7 @@ function normalizeOrder(raw) {
4538
4289
  }
4539
4290
 
4540
4291
  // src/metrics.ts
4541
- var TRADE_TOOLS2 = /* @__PURE__ */ new Set(["place_order", "cancel_order", "trade_crypto"]);
4292
+ var TRADE_TOOLS = /* @__PURE__ */ new Set(["place_order", "cancel_order", "trade_crypto"]);
4542
4293
  var MetricsCollector = class {
4543
4294
  metrics;
4544
4295
  logger;
@@ -4572,7 +4323,7 @@ var MetricsCollector = class {
4572
4323
  metric.errors++;
4573
4324
  this.metrics.totalErrors++;
4574
4325
  }
4575
- if (TRADE_TOOLS2.has(toolName) && !error) {
4326
+ if (TRADE_TOOLS.has(toolName) && !error) {
4576
4327
  this.metrics.totalTrades++;
4577
4328
  }
4578
4329
  }
@@ -4599,7 +4350,7 @@ var MetricsCollector = class {
4599
4350
 
4600
4351
  // src/server.ts
4601
4352
  function createToromarketClient(options) {
4602
- return new import_sdk9.ToromarketClient({
4353
+ return new import_sdk6.ToromarketClient({
4603
4354
  baseUrl: options.baseUrl,
4604
4355
  clientId: "mcp-server/0.2.0",
4605
4356
  ...options.token ? { token: options.token } : {},
@@ -4737,20 +4488,8 @@ function createServerFactory(options) {
4737
4488
  const metrics = new MetricsCollector(logger);
4738
4489
  const rateLimiter = new RateLimiter();
4739
4490
  const traceCollector = new TraceCollector(client, logger, `session-${Date.now()}`);
4740
- const complianceEnabled = !!process.env.TOROMARKET_COMPLIANCE_MODE;
4741
- const blockedRegions = (process.env.TOROMARKET_BLOCKED_REGIONS ?? "").split(",").map((r) => r.trim().toUpperCase()).filter(Boolean);
4742
- const velocityTradesPerMin = Number(process.env.TOROMARKET_VELOCITY_TRADES_PER_MIN) || 30;
4743
- const velocityTcPerHour = Number(process.env.TOROMARKET_VELOCITY_TC_PER_HOUR) || 5e4;
4744
- const complianceGate = new ComplianceGate(
4745
- { enabled: complianceEnabled, blockedRegions, velocityTradesPerMin, velocityTcPerHour },
4746
- logger
4747
- );
4748
4491
  const middlewares = [
4749
- new RegistrationThrottle(),
4750
4492
  rateLimiter,
4751
- new SpoofingDetector(),
4752
- complianceGate,
4753
- new SpendingGuardrails(options.maxSessionLoss),
4754
4493
  new ErrorBudget(),
4755
4494
  new AuditLogger(logger),
4756
4495
  traceCollector
@@ -4760,7 +4499,6 @@ function createServerFactory(options) {
4760
4499
  let currentTrustTier = null;
4761
4500
  const executeOptions = {
4762
4501
  traceCollector,
4763
- complianceGate,
4764
4502
  onTrustTier: (trustTier) => {
4765
4503
  currentTrustTier = trustTier;
4766
4504
  logger.info({ event: "trust_tier_applied", trustTier });
@@ -4801,8 +4539,7 @@ function createServerFactory(options) {
4801
4539
  notificationService.stop();
4802
4540
  notificationService = null;
4803
4541
  }
4804
- },
4805
- setRegion: (region) => complianceGate.setRegion(region)
4542
+ }
4806
4543
  };
4807
4544
  };
4808
4545
  return { logger, createMcpServer };
@@ -4889,10 +4626,6 @@ async function startHttpTransport(options) {
4889
4626
  entry = { transport: transport2, server: createdServer, lastActivity: Date.now() };
4890
4627
  sessions.set(newSessionId, entry);
4891
4628
  logger.info({ event: "session_created", sessionId: newSessionId });
4892
- const region = req.headers["cf-ipcountry"] ?? req.headers["x-vercel-ip-country"] ?? req.headers["x-country-code"];
4893
- if (region && createdServer.setRegion) {
4894
- createdServer.setRegion(region);
4895
- }
4896
4629
  transport2.onclose = () => {
4897
4630
  createdServer.cleanup();
4898
4631
  sessions.delete(newSessionId);
@@ -4987,8 +4720,6 @@ var baseUrl = readEnv("TOROMARKET_BASE_URL") ?? "https://api.toromarket.io";
4987
4720
  var token = readEnv("TOROMARKET_TOKEN");
4988
4721
  var apiKey = readEnv("TOROMARKET_API_KEY");
4989
4722
  var logLevel = readEnv("TOROMARKET_LOG_LEVEL") ?? "info";
4990
- var maxSessionLossStr = readEnv("TOROMARKET_MAX_SESSION_LOSS");
4991
- var maxSessionLoss = maxSessionLossStr ? Number(maxSessionLossStr) : void 0;
4992
4723
  var transport = readEnv("TOROMARKET_TRANSPORT") ?? "stdio";
4993
4724
  var httpPort = Number(readEnv("TOROMARKET_HTTP_PORT") ?? "3001");
4994
4725
  var agentSecret = readEnv("TOROMARKET_AGENT_SECRET");
@@ -5009,7 +4740,6 @@ var serverOptions = {
5009
4740
  ...token ? { token } : {},
5010
4741
  ...apiKey ? { apiKey } : {},
5011
4742
  ...agentSecret ? { agentSecret } : {},
5012
- ...maxSessionLoss ? { maxSessionLoss } : {},
5013
4743
  ...pollIntervalMs !== void 0 ? { pollIntervalMs } : {}
5014
4744
  };
5015
4745
  async function main() {