0x0x230x

Untitled

Jun 25th, 2025
19
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.14 KB | None | 0 0
  1. import { gigsService } from '@/lib/services/GigsService';
  2. import { createMcpHandler } from '@vercel/mcp-adapter';
  3. import { z } from 'zod';
  4. import { SupportedPlatforms } from '@/lib/types/gigs';
  5. import { formatUnits } from 'viem';
  6. import { calculateDuration } from '@/lib/utils/common';
  7. import { tokensService } from '@/lib/services/TokensService';
  8. import { env } from '@/lib/config/env';
  9.  
  10. const handler = createMcpHandler(
  11. (server) => {
  12. // TOOL 1 — list_gigs
  13. server.tool(
  14. 'list_gigs',
  15. 'List live gigs on Gigbot with optional filters for platform and gig type',
  16. {
  17. platform: z
  18. .enum(['x', 'farcaster'])
  19. .optional()
  20. .describe('The platform to filter gigs by'),
  21. gig_type: z
  22. .enum(['follow', 'boost'])
  23. .optional()
  24. .describe('The type of gig to filter by'),
  25. },
  26. async (input) => {
  27. const gigs = await gigsService.getGigs({
  28. status: ['active'],
  29. platforms: input.platform
  30. ? ([input.platform] as SupportedPlatforms[])
  31. : undefined,
  32. types: input.gig_type ? ([input.gig_type] as string[]) : undefined,
  33. });
  34.  
  35. const visibleGigs = gigs.slice(0, 6);
  36. const rest = gigs.length - visibleGigs.length;
  37.  
  38. const visibleText = visibleGigs.map((gig) => {
  39. const type = gig.gig_type?.id ?? 'unknown';
  40. const platform = gig.platform ?? 'unknown';
  41. const howToEarn = gig.how_to_earn
  42. ? gig.how_to_earn.slice(0, 120) +
  43. (gig.how_to_earn.length > 120 ? '...' : '')
  44. : '(no instructions)';
  45.  
  46. return [
  47. `🎯 **#${gig.id}** – ${type} gig on ${platform}`,
  48. `📝 How to Earn: ${howToEarn}`,
  49. '',
  50. ].join('\n');
  51. });
  52.  
  53. const farcasterCount = gigs.filter(
  54. (g) => g.platform === 'farcaster',
  55. ).length;
  56. const twitterCount = gigs.filter((g) => g.platform === 'x').length;
  57. const onchainCount = gigs.filter(
  58. (g) => g.platform === 'onchain',
  59. ).length;
  60.  
  61. const notableGigs = gigs
  62. .filter(
  63. (g) =>
  64. g.gig_type?.id === 'custom_bounty' ||
  65. BigInt(g.amount || '0') >= BigInt('30000000000000000'),
  66. )
  67. .slice(0, 2);
  68.  
  69. const notableText = notableGigs.length
  70. ? [
  71. '🏅 Some notable gigs:',
  72. ...notableGigs.map(
  73. (g) =>
  74. `• #${g.id}: ${
  75. g.how_to_earn?.slice(0, 60).replace(/\n/g, ' ') ||
  76. 'Special gig'
  77. }...`,
  78. ),
  79. '',
  80. ]
  81. : [];
  82.  
  83. const summaryText = [
  84. `📊 There are ${gigs.length} active gigs in total.`,
  85. '',
  86. `🧭 Platforms:`,
  87. `• Farcaster: ${farcasterCount}`,
  88. `• X (Twitter): ${twitterCount}`,
  89. `• Onchain: ${onchainCount}`,
  90. '',
  91. ...notableText,
  92. `Need details about any specific one? Just ask by number.`,
  93. ];
  94.  
  95. return {
  96. content: [
  97. {
  98. type: 'text',
  99. text: JSON.stringify(
  100. {
  101. result: [
  102. '🎉 Here are some active gigs on Gigbot:',
  103. '',
  104. ...visibleText,
  105. rest > 0 ? `...and ${rest} more gigs available.\n` : '',
  106. ...summaryText,
  107. ].join('\n'),
  108. },
  109. null,
  110. 2,
  111. ),
  112. },
  113. ],
  114. };
  115. },
  116. );
  117.  
  118. // TOOL 2 — get_gig_details
  119. server.tool(
  120. 'gig_details',
  121. 'Get detailed information for a gig by ID',
  122. {
  123. id: z.string(),
  124. },
  125. async ({ id }) => {
  126. const gig = await gigsService.getGig(Number(id));
  127.  
  128. if (!gig) {
  129. return {
  130. content: [
  131. {
  132. type: 'text',
  133. text: JSON.stringify(
  134. { error: `Gig with ID ${id} not found.` },
  135. null,
  136. 2,
  137. ),
  138. },
  139. ],
  140. };
  141. }
  142.  
  143. const duration = calculateDuration(gig.start_time_ms, gig.end_time_ms);
  144. const reward = `${formatUnits(
  145. BigInt(gig.amount),
  146. gig.token?.decimals ?? 18,
  147. )} ${gig.token?.symbol ?? gig.ticker}`;
  148.  
  149. const postLink = gig.action_params?.post?.url || gig.external_url || '';
  150. const instructions = gig.action_params?.post?.text ?? '';
  151.  
  152. return {
  153. content: [
  154. {
  155. type: 'text',
  156. text: JSON.stringify(
  157. {
  158. result: [
  159. `🎯 **Gig #${gig.id}** – ${gig.gig_type.id} gig on ${gig.platform}`,
  160. '',
  161. `💰 **Reward:** ${reward}`,
  162. `🕒 **Duration:** ${duration}`,
  163. '',
  164. `📝 **Task:**`,
  165. gig.how_to_earn,
  166. '',
  167. instructions ? `📋 **Instructions:**\n${instructions}` : '',
  168. '',
  169. gig.earning_criteria
  170. ? `📜 **Reward Criteria:**\n${gig.earning_criteria}`
  171. : '',
  172. '',
  173. `🚀 **Complete this gig via:**`,
  174. `• 🌐 Web: https://gigbot.xyz/gigs/${gig.id}`,
  175. `• ⚡️ Farcaster Miniapp: https://farcaster.xyz/miniapps/3FMRmta8BM3P/gigbot/gigs/${gig.id}`,
  176. `• 💬 Telegram Miniapp: https://t.me/gig_alerts_bot?startapp=${gig.id}`,
  177. '',
  178. postLink ? `🔗 **Related Link:** ${postLink}` : '',
  179. ]
  180. .filter(Boolean)
  181. .join('\n'),
  182. },
  183. null,
  184. 2,
  185. ),
  186. },
  187. ],
  188. };
  189. },
  190. );
  191.  
  192. // TOOL 3 - create gig
  193. server.tool(
  194. 'create_gig',
  195. 'Create a new gig',
  196. {
  197. gig_type: z
  198. .enum(['follow', 'boost'])
  199. .describe('The type of gig to create'),
  200. platform: z
  201. .enum(['x', 'farcaster'])
  202. .describe('The platform to create the gig on'),
  203. token: z.object({
  204. ticker: z
  205. .string()
  206. .describe('The ticker of the token to create the gig on'),
  207. usd_amount: z
  208. .number()
  209. .min(11)
  210. .describe(
  211. 'The USD amount of the token to create the gig on. Minimum 11 USD.',
  212. ),
  213. address: z.string().describe('The address of the token'),
  214. }),
  215. url: z
  216. .string()
  217. .describe(
  218. `
  219. The url of the post or profile to create the gig on.
  220. For Farcaster, should be farcaster.xyz.
  221. For X, it should be x.com.
  222. `,
  223. )
  224. .refine(
  225. (val) =>
  226. /^https?:\/\/(farcaster\.xyz|warpcast\.com|x\.com|twitter\.com)\//.test(
  227. val,
  228. ),
  229. {
  230. message:
  231. 'URL must be from farcaster.xyz OR x.com based on the choosen platform',
  232. },
  233. ),
  234. },
  235. async (input) => {
  236. const token = await tokensService.getTokenByAddress(
  237. input.token.address,
  238. );
  239.  
  240. if (!token) {
  241. return {
  242. content: [
  243. {
  244. type: 'text',
  245. text: JSON.stringify({ error: 'Token not supported' }, null, 2),
  246. },
  247. ],
  248. };
  249. }
  250.  
  251. const amount = input.token.usd_amount / parseFloat(token.price);
  252.  
  253. const createBody = JSON.stringify({
  254. gig_type: input.gig_type,
  255. platform: input.platform,
  256. url: input.url,
  257. duration: '1day',
  258. amount,
  259. start_time: new Date().toISOString(),
  260. token_address: token.address,
  261. });
  262.  
  263. try {
  264. // --- TEMP: simulate successful gig creation ---
  265. const response = await fetch(
  266. `${env.NEXT_PUBLIC_APP_BASE_URL}api/gigs/create`,
  267. {
  268. method: 'POST',
  269. body: createBody,
  270. headers: {
  271. 'Content-Type': 'application/json',
  272. 'gigbot-api-key': env.GIGS_API_KEY,
  273. },
  274. },
  275. );
  276.  
  277. if (!response.ok) {
  278. const errorText = await response.text();
  279. throw new Error(
  280. `Gig creation failed with status ${response.status}: ${errorText}`,
  281. );
  282. }
  283.  
  284. const data = await response.json();
  285.  
  286. return {
  287. content: [
  288. {
  289. type: 'text',
  290. text: [
  291. '🚀 Your gig has been created but is not yet live.',
  292. '',
  293. 'To activate it, please fund the gig wallet with the required tokens:',
  294. '',
  295. `• Gig ID: ${data.gig_id}`,
  296. `• Token: ${input.token.ticker} (${data.gig_token_address})`,
  297. `• Chain ID: ${data.gig_chain_id}`,
  298. `• Wallet Address: ${data.gig_wallet_address}`,
  299. `• Amount Required: ${data.total_amount} ${input.token.ticker}`,
  300. '',
  301. `💸 This covers your reward ($${input.token.usd_amount}) + platform fee.`,
  302. '',
  303. `Once funded, your gig will go live automatically.`,
  304. ].join('\n'),
  305. },
  306. ],
  307. };
  308. } catch (error: any) {
  309. return {
  310. content: [
  311. {
  312. type: 'text',
  313. text: JSON.stringify(
  314. {
  315. error: '❌ Failed to create gig.',
  316. reason: error.message || 'Unknown error occurred.',
  317. },
  318. null,
  319. 2,
  320. ),
  321. },
  322. ],
  323. };
  324. }
  325. },
  326. );
  327.  
  328. // TOOL 4 - supported tokens
  329. server.tool(
  330. 'tokens_supported',
  331. 'Show supported tokens for creating gigs (with symbol and address)',
  332. {},
  333. async () => {
  334. const tokens = await tokensService.getPortfolioTokens();
  335.  
  336. const text = tokens
  337. .map((t) => `• ${t.symbol} – (${t.address})`)
  338. .join('\n');
  339.  
  340. return {
  341. content: [
  342. {
  343. type: 'text',
  344. text: JSON.stringify(
  345. {
  346. result: [
  347. '💰 Here are the currently supported tokens for rewards:',
  348. '',
  349. text,
  350. ].join('\n'),
  351. },
  352. null,
  353. 2,
  354. ),
  355. },
  356. ],
  357. };
  358. },
  359. );
  360. },
  361. {
  362. capabilities: {
  363. tools: {
  364. list_gigs: {
  365. description: 'List gigs with optional platform and gig_type filters',
  366. },
  367. get_gig_details: {
  368. description: 'Get detailed information for a gig by ID',
  369. },
  370. create_gig: {
  371. description: 'Create a new gig',
  372. },
  373. tokens_supported: {
  374. description: 'Show supported tokens for creating gigs',
  375. },
  376. },
  377. },
  378. instructions: `
  379. You are an assistant for Gigbot. Your job is to help users discover and complete gigs easily.
  380.  
  381. You can:
  382. - List active gigs with optional filters (e.g., by platform or gig type)
  383. - Highlight recent or notable gigs
  384. - Show full details for any gig
  385. - Always include Web, Farcaster Miniapp, and Telegram Miniapp links
  386. - Never suggest manual completion steps
  387.  
  388. When listing:
  389. - Show a preview (e.g., 5–6 gigs) with type, platform, and short "How to Earn"
  390. - Summarize remaining gigs by platform (Farcaster / Twitter / Onchain)
  391. - Mention standout or high-reward gigs (e.g. token holding, special campaigns)
  392. - Offer to show full details for any gig by number
  393.  
  394. When showing gig details:
  395. - Include: Type, Platform, Duration, Reward
  396. - Show: How to Earn and Instructions (if any)
  397. - Always show:
  398. 🌐 Web → https://gigbot.xyz/gigs/[id]
  399. ⚡️ Farcaster Miniapp → https://farcaster.xyz/miniapps/3FMRmta8BM3P/gigbot/gigs/[id]
  400. 💬 Telegram Miniapp → https://t.me/gig_alerts_bot?startapp=[id]
  401. - Do not describe manual steps like "Go to Farcaster and follow…"
  402.  
  403. When creating a gig:
  404. - Always use the 'tokens_supported' tool to retrieve the full list of supported tokens with their metadata (ticker, address, chain, decimals).
  405. - From the user, only ask for 'ticker' and 'usd_amount'.
  406. - Never ask the user for the token address.
  407. - Internally look up the correct token address from the supported tokens list based on the provided ticker, and include it in the 'token' input.
  408. - If the user **does** provide a token address, **use that one** instead of the one from the supported list.
  409. - Ensure that 'token.usd_amount' >= 11, otherwise reject the request.
  410. - **Always confirm with the user before creating a gig.**
  411. - Show a summary: platform, type, URL, reward in USD and token
  412. - Ask explicitly: "Would you like me to create this gig?"
  413. - **Never create a gig on your own without explicit user confirmation.**
  414. - Do NOT automatically call 'list_gigs' or show existing gigs unless the user explicitly asks to see them.
  415. - Focus only on guiding the user through the required inputs for creating the gig.
  416.  
  417. When the user says "create another gig", "create one more", "create again", or similar:
  418. - Do NOT reuse previous inputs.
  419. - Instead, treat it as a new gig request.
  420. - Ask again for all required inputs:
  421. - Type of gig (follow or boost)
  422. - Platform (x or farcaster)
  423. - URL of the post or profile
  424. - Token ticker and USD amount
  425. - Only reuse previous values if the user explicitly says so (e.g., "same as before" or "same token").
  426. - If the user says "create gig" or "I want to make a gig", respond with a step-by-step prompt asking for the required input fields.
  427.  
  428.  
  429. When listing supported tokens:
  430. - Show the symbol and address
  431.  
  432.  
  433.  
  434. Always prioritize clarity and step-by-step input when helping the user create a gig.
  435. Do not take initiative or assume intent — always confirm before taking any action.
  436.  
  437. Keep messages brief, formatted, and actionable.`,
  438. },
  439. {
  440. redisUrl: process.env.REDIS_URL,
  441. basePath: '/api/mcp/gigs',
  442. maxDuration: 800,
  443. verboseLogs: true,
  444. },
  445. );
  446.  
  447. export const GET = async (request: Request) => {
  448. return handler(request);
  449. };
  450.  
  451. export const POST = async (request: Request) => {
  452. return handler(request);
  453. };
  454.  
  455. export const OPTIONS = async (request: Request) => {
  456. return handler(request);
  457. };
Advertisement
Add Comment
Please, Sign In to add comment