@torque-labs/mcp 0.4.0 → 0.4.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 CHANGED
@@ -142,11 +142,17 @@ You can provide either an auth token or a private key, or authenticate at runtim
142
142
  | `create_instruction` | Add an instruction to an already-uploaded IDL |
143
143
  | `list_idls` | List uploaded IDLs and their tracked instructions |
144
144
 
145
+ ### Dune Event Sources
146
+
147
+ | Tool | Description |
148
+ | ----------------------------- | --------------------------------------------------------------------------------------------- |
149
+ | `register_dune_event_source` | Register a saved Dune query as a daily-ingested event source on the active project. Requires the Dune MCP installed and the query saved as non-temp. Preview first, then set `confirmed: true` to execute. |
150
+
145
151
  ### Incentive Query Building
146
152
 
147
153
  | Tool | Description |
148
154
  | ---------------------------- | ---------------------------------------------------------------------------------------------------------------- |
149
- | `generate_incentive_query` | Generate SQL for any incentive source — token swaps, bonding curve trades, token holds, custom events, or IDL instructions |
155
+ | `generate_incentive_query` | Generate SQL for any incentive source — token swaps, bonding curve trades, token holds, custom events, IDL instructions, or registered Dune queries |
150
156
  | `preview_incentive_query` | CREATION-TIME ONLY — test/validate a query against real indexed data before creating an incentive (not for live results) |
151
157
 
152
158
  ### Recurring Incentives
@@ -225,6 +231,16 @@ For on-chain program interactions not covered by token actions.
225
231
  4. `preview_incentive_query` — (optional) validate results
226
232
  5. `create_recurring_incentive` — create the reward program
227
233
 
234
+ ### Dune Query Incentive
235
+
236
+ For activity tracked by a saved Dune Analytics query. Requires the [Dune MCP](https://docs.dune.com/mcp) installed alongside this server. The Dune query must be saved (non-temp) and return exactly one day of results per run, scoped by a single `DATE`-cast string parameter.
237
+
238
+ 1. `register_dune_event_source` — register the Dune query on the active project (preview, then `confirmed: true`)
239
+ 2. Wait for at least one daily ingestion run to populate indexed data
240
+ 3. `generate_incentive_query` with `source: "dune_query"` and `duneEventQueryId` set to the id returned by step 1 — build the SQL query
241
+ 4. `preview_incentive_query` — (optional) validate results
242
+ 5. `create_recurring_incentive` — create the reward program
243
+
228
244
  ## Development
229
245
 
230
246
  ### Setup
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ Examples:
25
25
  claude mcp add torque -- npx @torque-labs/mcp
26
26
  claude mcp add torque -e TORQUE_API_TOKEN=your-token -- npx @torque-labs/mcp
27
27
  npx @torque-labs/mcp --apiToken your-token
28
- `.trimStart();process.stderr.write(t)}function he(){process.stderr.write(`0.4.0
28
+ `.trimStart();process.stderr.write(t)}function he(){process.stderr.write(`0.4.1
29
29
  `)}var Qt="https://server.torque.so",Bt="https://platform.torque.so",Wt="https://ingest.torque.so",Gt="Sign in to Torque";function ge(t){let e=t.apiUrl??process.env.TORQUE_API_URL??Qt,r=t.platformUrl??process.env.TORQUE_PLATFORM_URL??Bt,n=t.ingesterUrl??process.env.TORQUE_INGESTER_URL??Wt,i=t.privateKey??process.env.WALLET_PRIVATE_KEY??void 0,o=t.apiToken??process.env.TORQUE_API_TOKEN??void 0;return{apiBaseUrl:e,platformUrl:r,ingesterUrl:n,signStatement:Gt,privateKey:i,apiToken:o}}import{McpServer as En}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Tn}from"@modelcontextprotocol/sdk/server/stdio.js";import{createKeyPairSignerFromBytes as Vt,getBase58Encoder as Ht,getTransactionDecoder as Kt,getTransactionEncoder as Yt,partiallySignTransaction as zt,signBytes as Jt}from"@solana/kit";async function ye(t){let e=t.trim(),r;if(e.startsWith("["))try{let i=JSON.parse(e);r=new Uint8Array(i)}catch{throw new Error("Invalid byte array format for private key")}else try{let o=Ht().encode(e);r=Uint8Array.from(o)}catch{throw new Error("Invalid private key format. Provide a base58-encoded key or JSON byte array.")}let n=await Vt(r);return{address:n.address,async signMessage(i){let o=await Jt(n.keyPair.privateKey,i);return new Uint8Array(o)},async signTransaction(i){let o=Kt(),s=Yt(),u=o.decode(i),c=await zt([n.keyPair],u);return new Uint8Array(s.encode(c))}}}import Q from"fs/promises";import B from"path";var W=class{token=null;authToken=null;baseUrl;cacheDir;constructor(e,r){this.baseUrl=e,this.cacheDir=r||B.join(import.meta.dirname,"..",".cache")}getToken(){return this.authToken??this.token}setAuthToken(e){this.authToken=e}getAuthHeader(){let e=this.getToken();return e?{Authorization:`Bearer ${e}`}:null}async setToken(e,r){this.token=e,await this.persistToken(e,r)}async clearToken(){this.token=null,this.authToken=null;let e=B.join(this.cacheDir,"token.json");try{await Q.unlink(e)}catch{}}async loadPersistedToken(){let e=B.join(this.cacheDir,"token.json");try{let r=await Q.readFile(e,"utf-8"),n=JSON.parse(r);this.token=n.token}catch{}}async login(e,r){return this.performLogin(e,"",r)}async loginWithStatement(e,r,n){return this.performLogin(e,r,n)}async performLogin(e,r,n){let i=await fetch(`${this.baseUrl}/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({authType:"basic",pubKey:e,payload:{input:r,output:n}})});if(!i.ok){let s=await i.text();throw new Error(`Login failed (${i.status}): ${s}`)}let o=await i.json();if(!o.token||typeof o.token!="string")throw new Error("Login response missing token field");return o.token}async verify(){let e=this.getToken();if(!e)return!1;try{return(await fetch(`${this.baseUrl}/verify`,{method:"GET",headers:{Authorization:`Bearer ${e}`}})).ok}catch{return!1}}async persistToken(e,r){await Q.mkdir(this.cacheDir,{recursive:!0});let n=B.join(this.cacheDir,"token.json"),i={token:e,publicKey:r,timestamp:Date.now()};await Q.writeFile(n,JSON.stringify(i,null,2))}};function J(t){let e=new W(t.apiBaseUrl),r=null,n=null,i=null;return t.apiToken&&e.setAuthToken(t.apiToken),{getWallet:async()=>{if(r)return r;if(!t.privateKey)throw new Error("No private key configured. Set WALLET_PRIVATE_KEY env var or privateKey in MCP config.");return r=await ye(t.privateKey),r},auth:{login:(o,s)=>e.login(o,s),loginWithStatement:(o,s,u)=>e.loginWithStatement(o,s,u),verify:()=>e.verify(),getToken:()=>e.getToken(),setToken:(o,s)=>e.setToken(o,s),clearToken:()=>e.clearToken(),loadPersistedToken:()=>e.loadPersistedToken(),setAuthToken:o=>e.setAuthToken(o),getAuthHeader:()=>e.getAuthHeader()},get activeProject(){return n},setActiveProject:o=>{o?.id!==n?.id&&(i=null),n=o},get aiContextCache(){return i},set aiContextCache(o){i=o},apiRequest:async(o,s)=>{let u=e.getAuthHeader();if(!u)throw new Error("Not authenticated. Use the authenticate tool first or set TORQUE_API_TOKEN.");let c=`${t.apiBaseUrl}${o}`,l=await fetch(c,{method:s?.method??"GET",headers:{...u,...s?.body?{"Content-Type":"application/json"}:{}},...s?.body?{body:JSON.stringify(s.body)}:{}});if(!l.ok){let p=await l.text();throw new Error(`API request failed (${l.status}): ${p}`)}return await l.json()},config:t}}import{z as mr}from"zod";import{getBase58Decoder as fr}from"@solana/kit";import{z as a}from"zod";var Ln=a.enum(["SUCCESS","BAD_REQUEST","NOT_AUTHORIZED","FORBIDDEN","NOT_FOUND","INTERNAL_ERROR"]);function b(t,e){let r=a.discriminatedUnion("status",[a.object({status:a.literal("SUCCESS"),data:t}),a.object({status:a.enum(["BAD_REQUEST","NOT_AUTHORIZED","FORBIDDEN","NOT_FOUND","INTERNAL_ERROR"]),message:a.string()})]).parse(e);if(r.status!=="SUCCESS")throw new Error(r.message);return r.data}function I(t,e){return b(a.array(t),e)}var On=a.enum(["TOKENS","SOL","POINTS"]),Un=a.enum(["DIRECT","MANUAL","CSV","FORMULA","RAFFLE"]),Xt=a.enum(["AIRDROP","CLAIM"]),Zt=a.enum(["EQUAL_CHANCES","WEIGHTED_BY_METRIC"]),ve=a.enum(["ALLOCATION","PERCENTAGE"]),er=a.enum(["UPCOMING","EVALUATING","CLAIMING","COMPLETED","FAILED"]),tr=a.enum(["DISTRIBUTED","PENDING","FAILED"]),rr=a.discriminatedUnion("type",[a.object({type:a.literal("REBATE"),maxPerParticipant:a.number().nullish()}),a.object({type:a.literal("RAFFLE"),maxPerParticipant:a.number().nullish()}),a.object({type:a.literal("COMPETITION")}),a.object({type:a.literal("DIRECT")})]),nr=a.discriminatedUnion("distributionType",[a.object({distributionType:a.literal("DIRECT"),audienceId:a.string().nullish(),csvFile:a.string().nullish()}),a.object({distributionType:a.literal("MANUAL"),valueType:ve,manualRanks:a.array(a.union([a.object({rank:a.number(),value:a.number()}),a.object({rankFrom:a.number(),rankTo:a.number(),value:a.number()})]))}),a.object({distributionType:a.literal("CSV"),valueType:ve.optional(),csvFile:a.string()}),a.object({distributionType:a.literal("FORMULA"),customFormula:a.string()}),a.object({distributionType:a.literal("RAFFLE"),selectionLogic:Zt,prizeBuckets:a.array(a.object({amount:a.number(),count:a.number()})),allowDuplicateWinners:a.boolean().optional()})]),ir=a.discriminatedUnion("emissionType",[a.object({emissionType:a.literal("TOKENS"),tokenAddress:a.string(),tokenDecimals:a.number()}),a.object({emissionType:a.literal("SOL")}),a.object({emissionType:a.literal("POINTS"),pointId:a.string()})]),or=a.object({totalFundAmount:a.number(),maxPerParticipant:a.number().nullish(),distributionMethod:Xt,claimWindowStart:a.coerce.date().optional(),claimWindowDuration:a.number(),timezoneOffset:a.number().optional()}),sr=nr.and(ir).and(or),ar=a.object({title:a.string(),description:a.string().optional(),image:a.string().optional(),primitive:rr.nullish()}),ur=a.object({id:a.string(),recurringOfferId:a.string().nullable(),epochNumber:a.number(),status:er,evalStart:a.coerce.date(),evalEnd:a.coerce.date(),claimStart:a.coerce.date(),claimEnd:a.coerce.date(),sqlQueryVersionId:a.string(),evalQueryFrequency:a.number(),distributionConfig:sr,offerMetadata:ar,sqlQueryId:a.string().nullable(),addressColumn:a.string().nullable(),metricColumn:a.string(),failureReason:a.string().nullish(),offerId:a.string().nullish(),createdAt:a.coerce.date(),updatedAt:a.coerce.date()}),cr=ur.extend({sqlQuery:a.object({id:a.string(),name:a.string()}).nullish()}),C=a.object({id:a.string(),name:a.string(),description:a.string().nullish(),type:a.string().nullish(),isActive:a.boolean(),maxIterations:a.number().nullish(),startDate:a.coerce.date().nullish(),timezone:a.string().optional(),createdAt:a.coerce.date(),updatedAt:a.coerce.date(),deleted:a.boolean(),userId:a.string(),projectId:a.string().nullish(),campaignId:a.string().nullish(),configs:a.array(cr).optional()}),lr=a.object({walletAddress:a.string(),metricValue:a.number()}),be=a.object({results:a.array(lr),total:a.number(),offset:a.number(),limit:a.number(),lastUpdated:a.coerce.date().nullable()}),dr=a.object({walletAddress:a.string(),allocatedAmount:a.number(),distributedAmount:a.number().nullable(),status:tr}),we=a.object({recipients:a.array(dr),total:a.number(),offset:a.number(),limit:a.number()}),q=a.object({id:a.string(),name:a.string(),description:a.string().nullish(),image:a.string().nullish(),defaultTokenAddress:a.string().nullish(),defaultProgramAddress:a.string().nullish(),createdAt:a.coerce.date(),updatedAt:a.coerce.date()}),Ee=a.object({id:a.string(),name:a.string().nullable(),key:a.string(),isActive:a.boolean(),lastUsedAt:a.coerce.date().nullable(),createdAt:a.coerce.date()}),Te=a.object({id:a.string(),name:a.string().nullable(),key:a.string()}),Ae=a.object({id:a.string(),name:a.string().optional()}),Re=a.object({queryId:a.string()}),_e=a.object({status:a.string(),preview:a.string().nullish(),error:a.string().nullish(),totalRows:a.number().nullish(),duration:a.number().nullish()}),Ie=a.object({url:a.string()}),j=a.object({id:a.string(),name:a.string(),eventName:a.string(),active:a.boolean(),deleted:a.boolean(),creatorId:a.string().nullable(),fields:a.array(a.object({fieldName:a.string(),type:a.enum(["string","number","boolean"]),label:a.string().optional(),description:a.string().optional()})),columnMapping:a.record(a.string(),a.string()).nullable(),createdAt:a.coerce.date(),updatedAt:a.coerce.date()}),Se=j.extend({projects:a.array(a.object({id:a.string(),projectId:a.string(),customEventId:a.string(),createdAt:a.coerce.date()}))}),pr=a.object({id:a.string(),instructionName:a.string(),label:a.string(),idlId:a.string(),projectId:a.string(),instruction:a.array(a.object({fieldName:a.string(),type:a.enum(["string","number","boolean"]),label:a.string().optional(),description:a.string().optional()})),fieldMapping:a.record(a.string(),a.string()),accountMapping:a.record(a.string(),a.string()),active:a.boolean().optional(),deleted:a.boolean().optional(),createdAt:a.coerce.date(),updatedAt:a.coerce.date()}),G=a.object({id:a.string(),projectId:a.string(),programAddress:a.string(),name:a.string(),displayName:a.string().nullable(),description:a.string().nullable(),idl:a.unknown(),types:a.unknown().nullable(),active:a.boolean(),deleted:a.boolean(),instructions:a.array(pr).nullish(),createdAt:a.coerce.date(),updatedAt:a.coerce.date()}),De=a.object({id:a.string(),instructionIds:a.array(a.string()).optional()}),xe=a.object({id:a.string()}),ke=a.object({addresses:a.object({programAddress:a.string().optional(),tokenAddress:a.string().optional()}).optional(),files:a.record(a.string(),a.string()).optional()});function X(t){if(t.length!==0)return t.reduce((e,r)=>r.epochNumber>e.epochNumber?r:e)}var k="\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501";function L(t){let e=`${t}/connect-mcp`;return["\u{1F510} **Authentication Required**","",`Visit [${e}](${e}) to create an account and get your auth token.`,"","Then use the `authenticate` tool with your auth token to connect."].join(`
30
30
  `)}function Pe(t,e){if(t.length===0)return["\u{1F4C1} **No projects found**","","Use `create_project` to create one."].join(`
31
31
  `);let r=[`\u{1F4C1} **Your Projects** (${t.length})`,"","| Project | ID | Status |","|---|---|---|"];for(let n of t){let i=n.id===e?"\u2705 active":"";r.push(`| **${n.name}** | \`${n.id}\` | ${i} |`)}return r.join(`
@@ -649,4 +649,4 @@ Claims are **fully server-side** \u2014 no transaction signing is required from
649
649
  **Frontend** handles wallet connection and all public endpoints \u2014 eval results display, offer info, claim triggering, and claim status polling. No secrets needed.
650
650
 
651
651
  **Backend** handles authenticated calls for epoch management, raw evaluation results, and any API-key-gated offer queries. Keep project auth tokens and API keys server-side only.
652
- `;function Ut(t){t.registerResource("building-landing-pages-guide","torque://building-landing-pages",{description:"Guide to building landing pages that display leaderboards, offer info, and claim status. Covers the domain model (projects, incentives, epochs, results), public REST endpoints for frontends, claim flow, auth requirements, and recommended frontend/backend architecture. Read this when helping a builder create a frontend for their incentive program.",mimeType:"text/markdown"},async()=>({contents:[{uri:"torque://building-landing-pages",mimeType:"text/markdown",text:wn}]}))}function An(t){if(!t.requires?.length)return t.description;let e=[];return t.requires.includes("auth")&&e.push("requires authentication"),t.requires.includes("activeProject")&&e.push("requires an active project via `set_active_project`"),`${t.description} [PREREQUISITE: ${e.join("; ")}. Call these first and wait for success.]`}function E(t,e,r){t.registerTool(e.name,{description:An(e),inputSchema:e.inputSchema},async n=>e.handler(n,r))}async function Mt(t){let e=new En({name:"torque-mcp",version:"0.4.0"}),r=J(t);await r.auth.loadPersistedToken(),E(e,We,r),E(e,Ge,r),E(e,Ve,r),E(e,He,r),E(e,Ke,r),E(e,Ye,r),E(e,ze,r),E(e,Je,r),E(e,rt,r),E(e,nt,r),E(e,it,r),E(e,ot,r),E(e,Nt,r),E(e,st,r),E(e,at,r),E(e,ut,r),E(e,ct,r),E(e,xt,r),E(e,kt,r),E(e,Pt,r),E(e,Ct,r),E(e,It,r),E(e,Dt,r),E(e,$t,r),E(e,qt,r),e.registerPrompt(z.name,z.config,z.handler),jt(e),Lt(e),Ot(e),Ut(e);let n=new Tn;await e.connect(n)}var{action:Ft,params:Rn}=me(process.argv.slice(2));Ft==="help"&&(fe(),process.exit(0));Ft==="version"&&(he(),process.exit(0));var _n=ge(Rn);Mt(_n).catch(t=>{console.error("Failed to start Torque MCP server:",t),process.exit(1)});
652
+ `;function Ut(t){t.registerResource("building-landing-pages-guide","torque://building-landing-pages",{description:"Guide to building landing pages that display leaderboards, offer info, and claim status. Covers the domain model (projects, incentives, epochs, results), public REST endpoints for frontends, claim flow, auth requirements, and recommended frontend/backend architecture. Read this when helping a builder create a frontend for their incentive program.",mimeType:"text/markdown"},async()=>({contents:[{uri:"torque://building-landing-pages",mimeType:"text/markdown",text:wn}]}))}function An(t){if(!t.requires?.length)return t.description;let e=[];return t.requires.includes("auth")&&e.push("requires authentication"),t.requires.includes("activeProject")&&e.push("requires an active project via `set_active_project`"),`${t.description} [PREREQUISITE: ${e.join("; ")}. Call these first and wait for success.]`}function E(t,e,r){t.registerTool(e.name,{description:An(e),inputSchema:e.inputSchema},async n=>e.handler(n,r))}async function Mt(t){let e=new En({name:"torque-mcp",version:"0.4.1"}),r=J(t);await r.auth.loadPersistedToken(),E(e,We,r),E(e,Ge,r),E(e,Ve,r),E(e,He,r),E(e,Ke,r),E(e,Ye,r),E(e,ze,r),E(e,Je,r),E(e,rt,r),E(e,nt,r),E(e,it,r),E(e,ot,r),E(e,Nt,r),E(e,st,r),E(e,at,r),E(e,ut,r),E(e,ct,r),E(e,xt,r),E(e,kt,r),E(e,Pt,r),E(e,Ct,r),E(e,It,r),E(e,Dt,r),E(e,$t,r),E(e,qt,r),e.registerPrompt(z.name,z.config,z.handler),jt(e),Lt(e),Ot(e),Ut(e);let n=new Tn;await e.connect(n)}var{action:Ft,params:Rn}=me(process.argv.slice(2));Ft==="help"&&(fe(),process.exit(0));Ft==="version"&&(he(),process.exit(0));var _n=ge(Rn);Mt(_n).catch(t=>{console.error("Failed to start Torque MCP server:",t),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torque-labs/mcp",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "MCP server for the Torque incentive platform on Solana — create and manage token reward programs through AI assistants",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",