Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { gigsService } from '@/lib/services/GigsService';
- import { createMcpHandler } from '@vercel/mcp-adapter';
- import { z } from 'zod';
- import { SupportedPlatforms } from '@/lib/types/gigs';
- import { formatUnits } from 'viem';
- import { calculateDuration } from '@/lib/utils/common';
- import { tokensService } from '@/lib/services/TokensService';
- import { env } from '@/lib/config/env';
- const handler = createMcpHandler(
- (server) => {
- // TOOL 1 — list_gigs
- server.tool(
- 'list_gigs',
- 'List live gigs on Gigbot with optional filters for platform and gig type',
- {
- platform: z
- .enum(['x', 'farcaster'])
- .optional()
- .describe('The platform to filter gigs by'),
- gig_type: z
- .enum(['follow', 'boost'])
- .optional()
- .describe('The type of gig to filter by'),
- },
- async (input) => {
- const gigs = await gigsService.getGigs({
- status: ['active'],
- platforms: input.platform
- ? ([input.platform] as SupportedPlatforms[])
- : undefined,
- types: input.gig_type ? ([input.gig_type] as string[]) : undefined,
- });
- const visibleGigs = gigs.slice(0, 6);
- const rest = gigs.length - visibleGigs.length;
- const visibleText = visibleGigs.map((gig) => {
- const type = gig.gig_type?.id ?? 'unknown';
- const platform = gig.platform ?? 'unknown';
- const howToEarn = gig.how_to_earn
- ? gig.how_to_earn.slice(0, 120) +
- (gig.how_to_earn.length > 120 ? '...' : '')
- : '(no instructions)';
- return [
- `🎯 **#${gig.id}** – ${type} gig on ${platform}`,
- `📝 How to Earn: ${howToEarn}`,
- '',
- ].join('\n');
- });
- const farcasterCount = gigs.filter(
- (g) => g.platform === 'farcaster',
- ).length;
- const twitterCount = gigs.filter((g) => g.platform === 'x').length;
- const onchainCount = gigs.filter(
- (g) => g.platform === 'onchain',
- ).length;
- const notableGigs = gigs
- .filter(
- (g) =>
- g.gig_type?.id === 'custom_bounty' ||
- BigInt(g.amount || '0') >= BigInt('30000000000000000'),
- )
- .slice(0, 2);
- const notableText = notableGigs.length
- ? [
- '🏅 Some notable gigs:',
- ...notableGigs.map(
- (g) =>
- `• #${g.id}: ${
- g.how_to_earn?.slice(0, 60).replace(/\n/g, ' ') ||
- 'Special gig'
- }...`,
- ),
- '',
- ]
- : [];
- const summaryText = [
- `📊 There are ${gigs.length} active gigs in total.`,
- '',
- `🧭 Platforms:`,
- `• Farcaster: ${farcasterCount}`,
- `• X (Twitter): ${twitterCount}`,
- `• Onchain: ${onchainCount}`,
- '',
- ...notableText,
- `Need details about any specific one? Just ask by number.`,
- ];
- return {
- content: [
- {
- type: 'text',
- text: JSON.stringify(
- {
- result: [
- '🎉 Here are some active gigs on Gigbot:',
- '',
- ...visibleText,
- rest > 0 ? `...and ${rest} more gigs available.\n` : '',
- ...summaryText,
- ].join('\n'),
- },
- null,
- 2,
- ),
- },
- ],
- };
- },
- );
- // TOOL 2 — get_gig_details
- server.tool(
- 'gig_details',
- 'Get detailed information for a gig by ID',
- {
- id: z.string(),
- },
- async ({ id }) => {
- const gig = await gigsService.getGig(Number(id));
- if (!gig) {
- return {
- content: [
- {
- type: 'text',
- text: JSON.stringify(
- { error: `Gig with ID ${id} not found.` },
- null,
- 2,
- ),
- },
- ],
- };
- }
- const duration = calculateDuration(gig.start_time_ms, gig.end_time_ms);
- const reward = `${formatUnits(
- BigInt(gig.amount),
- gig.token?.decimals ?? 18,
- )} ${gig.token?.symbol ?? gig.ticker}`;
- const postLink = gig.action_params?.post?.url || gig.external_url || '';
- const instructions = gig.action_params?.post?.text ?? '';
- return {
- content: [
- {
- type: 'text',
- text: JSON.stringify(
- {
- result: [
- `🎯 **Gig #${gig.id}** – ${gig.gig_type.id} gig on ${gig.platform}`,
- '',
- `💰 **Reward:** ${reward}`,
- `🕒 **Duration:** ${duration}`,
- '',
- `📝 **Task:**`,
- gig.how_to_earn,
- '',
- instructions ? `📋 **Instructions:**\n${instructions}` : '',
- '',
- gig.earning_criteria
- ? `📜 **Reward Criteria:**\n${gig.earning_criteria}`
- : '',
- '',
- `🚀 **Complete this gig via:**`,
- `• 🌐 Web: https://gigbot.xyz/gigs/${gig.id}`,
- `• ⚡️ Farcaster Miniapp: https://farcaster.xyz/miniapps/3FMRmta8BM3P/gigbot/gigs/${gig.id}`,
- `• 💬 Telegram Miniapp: https://t.me/gig_alerts_bot?startapp=${gig.id}`,
- '',
- postLink ? `🔗 **Related Link:** ${postLink}` : '',
- ]
- .filter(Boolean)
- .join('\n'),
- },
- null,
- 2,
- ),
- },
- ],
- };
- },
- );
- // TOOL 3 - create gig
- server.tool(
- 'create_gig',
- 'Create a new gig',
- {
- gig_type: z
- .enum(['follow', 'boost'])
- .describe('The type of gig to create'),
- platform: z
- .enum(['x', 'farcaster'])
- .describe('The platform to create the gig on'),
- token: z.object({
- ticker: z
- .string()
- .describe('The ticker of the token to create the gig on'),
- usd_amount: z
- .number()
- .min(11)
- .describe(
- 'The USD amount of the token to create the gig on. Minimum 11 USD.',
- ),
- address: z.string().describe('The address of the token'),
- }),
- url: z
- .string()
- .describe(
- `
- The url of the post or profile to create the gig on.
- For Farcaster, should be farcaster.xyz.
- For X, it should be x.com.
- `,
- )
- .refine(
- (val) =>
- /^https?:\/\/(farcaster\.xyz|warpcast\.com|x\.com|twitter\.com)\//.test(
- val,
- ),
- {
- message:
- 'URL must be from farcaster.xyz OR x.com based on the choosen platform',
- },
- ),
- },
- async (input) => {
- const token = await tokensService.getTokenByAddress(
- input.token.address,
- );
- if (!token) {
- return {
- content: [
- {
- type: 'text',
- text: JSON.stringify({ error: 'Token not supported' }, null, 2),
- },
- ],
- };
- }
- const amount = input.token.usd_amount / parseFloat(token.price);
- const createBody = JSON.stringify({
- gig_type: input.gig_type,
- platform: input.platform,
- url: input.url,
- duration: '1day',
- amount,
- start_time: new Date().toISOString(),
- token_address: token.address,
- });
- try {
- // --- TEMP: simulate successful gig creation ---
- const response = await fetch(
- `${env.NEXT_PUBLIC_APP_BASE_URL}api/gigs/create`,
- {
- method: 'POST',
- body: createBody,
- headers: {
- 'Content-Type': 'application/json',
- 'gigbot-api-key': env.GIGS_API_KEY,
- },
- },
- );
- if (!response.ok) {
- const errorText = await response.text();
- throw new Error(
- `Gig creation failed with status ${response.status}: ${errorText}`,
- );
- }
- const data = await response.json();
- return {
- content: [
- {
- type: 'text',
- text: [
- '🚀 Your gig has been created but is not yet live.',
- '',
- 'To activate it, please fund the gig wallet with the required tokens:',
- '',
- `• Gig ID: ${data.gig_id}`,
- `• Token: ${input.token.ticker} (${data.gig_token_address})`,
- `• Chain ID: ${data.gig_chain_id}`,
- `• Wallet Address: ${data.gig_wallet_address}`,
- `• Amount Required: ${data.total_amount} ${input.token.ticker}`,
- '',
- `💸 This covers your reward ($${input.token.usd_amount}) + platform fee.`,
- '',
- `Once funded, your gig will go live automatically.`,
- ].join('\n'),
- },
- ],
- };
- } catch (error: any) {
- return {
- content: [
- {
- type: 'text',
- text: JSON.stringify(
- {
- error: '❌ Failed to create gig.',
- reason: error.message || 'Unknown error occurred.',
- },
- null,
- 2,
- ),
- },
- ],
- };
- }
- },
- );
- // TOOL 4 - supported tokens
- server.tool(
- 'tokens_supported',
- 'Show supported tokens for creating gigs (with symbol and address)',
- {},
- async () => {
- const tokens = await tokensService.getPortfolioTokens();
- const text = tokens
- .map((t) => `• ${t.symbol} – (${t.address})`)
- .join('\n');
- return {
- content: [
- {
- type: 'text',
- text: JSON.stringify(
- {
- result: [
- '💰 Here are the currently supported tokens for rewards:',
- '',
- text,
- ].join('\n'),
- },
- null,
- 2,
- ),
- },
- ],
- };
- },
- );
- },
- {
- capabilities: {
- tools: {
- list_gigs: {
- description: 'List gigs with optional platform and gig_type filters',
- },
- get_gig_details: {
- description: 'Get detailed information for a gig by ID',
- },
- create_gig: {
- description: 'Create a new gig',
- },
- tokens_supported: {
- description: 'Show supported tokens for creating gigs',
- },
- },
- },
- instructions: `
- You are an assistant for Gigbot. Your job is to help users discover and complete gigs easily.
- You can:
- - List active gigs with optional filters (e.g., by platform or gig type)
- - Highlight recent or notable gigs
- - Show full details for any gig
- - Always include Web, Farcaster Miniapp, and Telegram Miniapp links
- - Never suggest manual completion steps
- When listing:
- - Show a preview (e.g., 5–6 gigs) with type, platform, and short "How to Earn"
- - Summarize remaining gigs by platform (Farcaster / Twitter / Onchain)
- - Mention standout or high-reward gigs (e.g. token holding, special campaigns)
- - Offer to show full details for any gig by number
- When showing gig details:
- - Include: Type, Platform, Duration, Reward
- - Show: How to Earn and Instructions (if any)
- - Always show:
- 🌐 Web → https://gigbot.xyz/gigs/[id]
- ⚡️ Farcaster Miniapp → https://farcaster.xyz/miniapps/3FMRmta8BM3P/gigbot/gigs/[id]
- 💬 Telegram Miniapp → https://t.me/gig_alerts_bot?startapp=[id]
- - Do not describe manual steps like "Go to Farcaster and follow…"
- When creating a gig:
- - Always use the 'tokens_supported' tool to retrieve the full list of supported tokens with their metadata (ticker, address, chain, decimals).
- - From the user, only ask for 'ticker' and 'usd_amount'.
- - Never ask the user for the token address.
- - Internally look up the correct token address from the supported tokens list based on the provided ticker, and include it in the 'token' input.
- - If the user **does** provide a token address, **use that one** instead of the one from the supported list.
- - Ensure that 'token.usd_amount' >= 11, otherwise reject the request.
- - **Always confirm with the user before creating a gig.**
- - Show a summary: platform, type, URL, reward in USD and token
- - Ask explicitly: "Would you like me to create this gig?"
- - **Never create a gig on your own without explicit user confirmation.**
- - Do NOT automatically call 'list_gigs' or show existing gigs unless the user explicitly asks to see them.
- - Focus only on guiding the user through the required inputs for creating the gig.
- When the user says "create another gig", "create one more", "create again", or similar:
- - Do NOT reuse previous inputs.
- - Instead, treat it as a new gig request.
- - Ask again for all required inputs:
- - Type of gig (follow or boost)
- - Platform (x or farcaster)
- - URL of the post or profile
- - Token ticker and USD amount
- - Only reuse previous values if the user explicitly says so (e.g., "same as before" or "same token").
- - 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.
- When listing supported tokens:
- - Show the symbol and address
- Always prioritize clarity and step-by-step input when helping the user create a gig.
- Do not take initiative or assume intent — always confirm before taking any action.
- Keep messages brief, formatted, and actionable.`,
- },
- {
- redisUrl: process.env.REDIS_URL,
- basePath: '/api/mcp/gigs',
- maxDuration: 800,
- verboseLogs: true,
- },
- );
- export const GET = async (request: Request) => {
- return handler(request);
- };
- export const POST = async (request: Request) => {
- return handler(request);
- };
- export const OPTIONS = async (request: Request) => {
- return handler(request);
- };
Advertisement
Add Comment
Please, Sign In to add comment