Torque MCP — Friction Log
Project: Surge (Signal-to-execution trading intelligence)
Developer: @surge-team
Date: 2026-04-16
Track: Torque MCP ($3,000 prize)
Setup
MCP Server Installation
claude mcp add torque -- npx @torque-labs/mcp
What worked: The npx @torque-labs/mcp command launches correctly. MCP tools are available in Claude Code after restart.
Friction: The MCP server requires an active Torque API key to do anything meaningful. Without TORQUE_API_KEY set, all tool calls return authentication errors rather than fixture/demo responses. For hackathon development, a sandbox mode that returns mock responses would let builders wire events before obtaining a key.
Event Schema Creation
Used create_custom_event to create 5 event schemas:
surge_trade_executed — fired after every swap, with tokenIn, tokenOut, amountUsd, strategy, signalConviction, txSignature
surge_limit_order_created — fired when a Jupiter limit order is created
surge_dca_created — fired when a Jupiter DCA is started
surge_lp_zap_in — fired after LP Agent zap-in via landing endpoint
surge_lp_zap_out — fired after LP Agent zap-out via landing endpoint
Friction: There is no schema validation feedback at creation time — the event schema is accepted even if field names conflict with reserved Torque fields. Only discovered this after emitting events and seeing unexpected field merging in the dashboard.
Friction: The MCP tool create_custom_event doesn't return the schema ID in a stable format. Subsequent attach_custom_event calls require the schema name string, not an ID, but the docs describe an ID-based approach. Spent time reconciling the two patterns.
Incentive Primitives
Leaderboard
Created via generate_incentive_query:
- Event:
surge_trade_executed
- Rank by: event count
- Period: weekly (Mon 00:00 UTC → Sun 23:59 UTC)
- Top 10 wallets displayed in Surge Rewards panel
Friction: The generate_incentive_query tool produces a SQL-like query fragment that must be submitted separately to create the actual leaderboard. The two-step process (generate → create) is not documented in the MCP tool descriptions — had to discover the create_recurring_incentive tool exists via tab-completion.
Rebate
Created via create_recurring_incentive:
- Type:
rebate
- Event:
surge_trade_executed
- Rate: 50 bps of
amountUsd field per event
- Settlement: weekly
Friction: The amountUsd field in the rebate formula must match exactly the field name in the event data object. There is no validation that the referenced field exists in the event schema — a typo silently results in $0 rebates.
Raffle
Created via create_recurring_incentive:
- Type:
raffle
- Event:
surge_trade_executed
- Eligibility: wallets with ≥3 events in the current period
- Draw: weekly
Friction: Raffle eligibility conditions use a different DSL than the leaderboard query. The documentation conflates the two. Found the correct syntax only by reading the MCP tool's schema definition in the returned JSON.
API Endpoint Discovery
The MCP tools cover setup (event schema creation, incentive creation). For emitting events from the application, a REST API is needed.
Friction (critical): The Torque MCP documentation does not specify the REST API endpoint for event emission. The tools assume events are emitted via another Torque SDK, but for a Next.js app calling server-side routes, we need an HTTP endpoint.
First attempt (wrong): Based on the brand domain we guessed POST https://api.torque.so/v1/custom-events with Authorization: Bearer <key>. Every request returned HTTP 530 Cloudflare — api.torque.so resolves but has no live origin behind it. Forty minutes wasted before we suspected the path itself was wrong.
How we found the truth: npm view @torque-labs/mcp revealed three CLI flags exposing the real hosts:
--apiUrl (default https://server.torque.so)
--platformUrl (default https://platform.torque.so)
--ingesterUrl (default https://ingest.torque.so) ← this is the one for events
We extracted the MCP package (npm pack @torque-labs/mcp), grepped the bundled JS, and found the route templates: /event/custom, /events, /offer/${id}/journey/leaderboard, etc. From there it took two probes to land the schema.
Confirmed contract (works as of 2026-04-29):
POST https://ingest.torque.so/events
Headers:
x-api-key: <TORQUE_API_KEY>
Content-Type: application/json
Body:
{ "eventName": "<schema_name>",
"userPubkey": "<wallet_base58>", // NOT "userWallet"
"timestamp": "<ISO-8601>", // required
"data": { ... } }
Wrong-guess pitfalls we hit:
- Host
api.torque.so returns 530 (Cloudflare can't reach origin — domain isn't wired to a server).
- Auth
Authorization: Bearer … is rejected with 401 "Missing x-api-key header"; only x-api-key: works.
- Body field
userWallet is rejected with body/userPubkey Required — must be userPubkey.
- Without
timestamp, request is rejected with Invalid date.
- A 400
"Event not found for this API key" after auth+schema pass means the event schema was never registered for that key — call MCP create_custom_event first.
Leaderboard endpoint (also undocumented for REST):
GET https://server.torque.so/offer/all
GET https://server.torque.so/offer/{offerId}/journey/leaderboard
Headers: x-api-key: <TORQUE_API_KEY>
Leaderboards are scoped to a recurring incentive (offer), not a project-wide event filter — you must create an offer via MCP create_recurring_incentive before there's anything to query. A fresh project responds 500 "Failed to parse offers" from /offer/all until at least one incentive exists.
Recommendation: Torque's MCP quickstart should include a one-page "emit from your app" reference covering: the three hosts, the x-api-key header, the userPubkey/timestamp/data body shape, and the prerequisite that the event schema exists for the API key. As written today, the only path to this information is decompiling the MCP CLI source.
What Worked Well
- MCP tool discovery via Claude Code tab-completion was fast
create_custom_event with typed field schemas was clear
- Fixture mode (
console.log('[Torque]' ...)) worked perfectly for frontend demo development
- Fire-and-forget pattern (
void emitEvent(...)) keeps execution flows clean
What Needs Work (Prioritized)
- Sandbox mode: All MCP tools should work without a live API key, returning fixture/mock responses for development
- REST API docs: Document the three hostnames (
ingest.torque.so, server.torque.so, platform.torque.so), the x-api-key header, and the canonical event body shape ({ eventName, userPubkey, timestamp, data }) alongside the MCP setup tools — today the only way to discover them is reading the bundled MCP CLI source
- Schema validation:
create_custom_event should validate field names against reserved fields and warn on mismatch
- Incentive DSL consistency: Raffle, rebate, and leaderboard query syntax should use the same DSL
- Schema ID stability: Event schemas should return a stable ID for use in
attach_custom_event instead of requiring string name matching
Demo Verification
All five event types fire correctly in fixture mode:
# Start dev server
npm run dev
# Execute a swap → triggers surge_trade_executed
# Console output:
[Torque] {
"event": "surge_trade_executed",
"userWallet": "<wallet>",
"timestamp": "2026-04-16T...",
"data": { "tokenIn": "USDC", "tokenOut": "SOL", "amountUsd": 50, "strategy": "swap" }
}
# Open LP position → triggers surge_lp_zap_in
[Torque] {
"event": "surge_lp_zap_in",
"userWallet": "<wallet>",
"data": { "strategy": "lp-zap-in", "poolAddress": "HJPjoWUrhoZzkNfRpHuieeFk9WcZWjwy6PBjZ81ngndJ" }
}
Rewards panel loads top 5 leaderboard entries from fixture data and will switch to live Torque data when TORQUE_API_KEY is set.
Resolution (2026-04-27)
After this friction log was filed, the integration was hardened in three places:
- Endpoint is now env-overridable.
lib/torque/client.ts reads
TORQUE_API_BASE (default https://api.torque.so/v1) and
TORQUE_EVENTS_PATH (default /custom-events). When @smicktrq confirms
the canonical path, the deploy gets one env-var change — no code touched.
- Schema validation at emit time.
validateSurgeEvent in
lib/torque/events.ts checks every payload against the registered event
schemas before fetch. Bad payloads log a warning and are dropped client-side
instead of producing $0 rebates silently — friction item #2 closed.
- Live leaderboard / profile with local fallback.
app/api/torque/leaderboard/route.ts and
app/api/torque/profile/route.ts now try the live Torque API first and
gracefully fall back to the local Surge event log on 404 / unreachable.
Dashboard panels stay populated whether or not the inferred REST shape is
correct — friction item #3 closed.
- Brand sweep. Five event names renamed
kairos_* → surge_*. User-side
action: re-run create_custom_event via MCP for each renamed event before
firing live traffic.
- Gated live test.
tests/torque-live.test.ts emits a synthetic
surge_trade_executed and fails loudly with a recognisable message if the
endpoint returns 404, so URL drift surfaces during CI rather than during
demo.