Skip to main content
Glama

create_ticket

Create tickets in GitHub, Jira, or Linear to transform ideas into actionable items. Converts captured feedback into trackable issues with titles, descriptions, and priorities.

Instructions

COMMIT an idea to GitHub, Jira, or Linear. This makes it real.

This is the final step in the IdeaLift flow: Capture → Normalize → COMMIT.

USE this tool when user says:

  • "Commit to GitHub/Jira/Linear"

  • "Commit it", "create this", "file this", "ship it", "make it real"

  • "Push this to [destination]"

  • A number from the commit options (e.g., "1" for GitHub)

  • "1", "2", or "3" after seeing commit options

IMPORTANT: If a normalize_idea was just called, use the draftId from that response. If no draftId is available, pass the full idea object from the normalize output. NEVER ask the user to re-provide content that was just normalized.

After successful commit, confirm with:

  • The ticket URL (clickable)

  • The ticket ID

  • A clear "COMMITTED" confirmation

This creates a REAL ticket. The user's idea now exists in their system.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
destinationYesWhere to create the ticket (github, jira, or linear)
ideaNo
destinationConfigNo
forceNoSkip duplicate check and create anyway
draftIdNoDraft ID from normalize_idea response. Use this instead of idea if available.

Implementation Reference

  • The `handleCreateTicket` function manages the MCP tool logic for creating tickets. It validates input, resolves draft ideas, checks for duplicates, and interacts with the `idealiftClient` to create the ticket.
    export async function handleCreateTicket(
      args: Record<string, unknown>,
      chatgptSubjectId?: string
    ): Promise<{ structuredContent: CreateTicketResult; content: string; _meta?: Record<string, unknown> }> {
      const idealiftUrl = process.env.IDEALIFT_APP_URL || 'https://idealift.app';
    
      // Check authentication
      if (!chatgptSubjectId) {
        return {
          structuredContent: {
            success: false,
            error: 'Not authenticated',
          },
          content: `## Connect IdeaLift to Create Tickets
    
    To create real tickets in GitHub, Jira, or Linear, connect your IdeaLift account first.
    
    **Your idea is ready** - once connected, I can create the ticket immediately!
    
    This takes about 30 seconds and you only need to do it once.`,
          _meta: {
            'mcp/www_authenticate': `Bearer realm="IdeaLift"`,
          },
        };
      }
    
      // Validate input
      const input = CreateTicketInput.parse(args);
    
      // Resolve idea from input, draftId, or most recent draft
      let idea = input.idea;
      let usedDraftId = input.draftId;
    
      if (!idea && input.draftId) {
        // Lookup draft by ID
        console.log('[Create Ticket] Looking up draft:', input.draftId);
        const draft = await idealiftClient.getDraft(input.draftId);
        if (draft.found && draft.normalizedData) {
          idea = {
            title: draft.title!,
            summary: draft.summary!,
            keyPoints: (draft.normalizedData as any).acceptanceCriteria || (draft.normalizedData as any).keyPoints,
            category: (draft.normalizedData as any).category,
            priority: (draft.normalizedData as any).priority,
          };
          console.log('[Create Ticket] Found draft:', draft.draftId);
        }
      }
    
      if (!idea) {
        // Try to get most recent draft for this user
        console.log('[Create Ticket] No idea or draftId, checking for recent draft...');
        const recentDraft = await idealiftClient.getDraft(undefined, chatgptSubjectId);
        if (recentDraft.found && recentDraft.normalizedData) {
          idea = {
            title: recentDraft.title!,
            summary: recentDraft.summary!,
            keyPoints: (recentDraft.normalizedData as any).acceptanceCriteria || (recentDraft.normalizedData as any).keyPoints,
            category: (recentDraft.normalizedData as any).category,
            priority: (recentDraft.normalizedData as any).priority,
          };
          usedDraftId = recentDraft.draftId;
          console.log('[Create Ticket] Using recent draft:', recentDraft.draftId);
        }
      }
    
      if (!idea) {
        return {
          structuredContent: {
            success: false,
            error: 'No idea to commit',
          },
          content: `## No Idea to Commit
    
    I don't have an idea ready to commit. Please either:
    - Normalize an idea first: "normalize this: [your idea]"
    - Provide the idea directly in your commit request
    
    Once you have a normalized idea, just say "commit to ${input.destination}" and I'll create the ticket.`,
        };
      }
    
      try {
        // Check for duplicates first (unless force is true)
        if (!input.force) {
          const dupeCheck = await idealiftClient.checkDuplicates(
            chatgptSubjectId,
            idea.title,
            idea.summary
          );
    
          if (dupeCheck.found && dupeCheck.similarity && dupeCheck.similarity > 0.85) {
            return {
              structuredContent: {
                success: false,
                duplicate: dupeCheck,
                error: 'Similar idea already exists',
              },
              content: `## Possible Duplicate Found
    
    A similar idea already exists (${Math.round((dupeCheck.similarity || 0) * 100)}% match):
    
    **[${dupeCheck.existingUrl}](${dupeCheck.existingUrl})**
    
    **Options:**
    - Say "create anyway" to create this as a new ticket
    - Say "show me the existing one" to review it first
    - Modify your idea to differentiate it`,
            };
          }
        }
    
        // Create the ticket
        const result = await idealiftClient.createTicket(
          chatgptSubjectId,
          input.destination,
          idea,
          input.destinationConfig
        );
    
        if (result.success) {
          // Mark draft as committed
          if (usedDraftId) {
            try {
              await idealiftClient.markDraftCommitted(usedDraftId, input.destination, result.ticketUrl);
              console.log('[Create Ticket] Marked draft as committed:', usedDraftId);
            } catch (markError) {
              console.error('[Create Ticket] Failed to mark draft committed:', markError);
            }
          }
    
          const destName = input.destination.charAt(0).toUpperCase() + input.destination.slice(1);
          return {
            structuredContent: result,
            content: `## ✅ COMMITTED to ${destName}!
    
    **[${result.ticketId}](${result.ticketUrl})**
    
    ${idea.title}
    
    ---
    *Tracked in IdeaLift: ${result.ideaLiftId}*
    
    **What's next?**
    - Paste another idea to normalize
    - Say "show my destinations" to see where else you can commit`,
          };
        } else {
          // Handle specific error cases
          if (result.error?.includes('not connected') || result.error?.includes('integration')) {
            return {
              structuredContent: result,
              content: `## ${input.destination.charAt(0).toUpperCase() + input.destination.slice(1)} Not Connected
    
    You haven't connected ${input.destination.charAt(0).toUpperCase() + input.destination.slice(1)} to IdeaLift yet.
    
    Say **"connect ${input.destination}"** and I'll walk you through it (takes about 30 seconds).
    
    Your idea is saved - we can create the ticket right after you connect!`,
            };
          }
    
          return {
            structuredContent: result,
            content: `## Couldn't Create Ticket
    
    ${result.error || 'Something went wrong.'}
    
    **Try:**
    - Say "show my destinations" to verify your connections
    - Check that you have access to the target repo/project
    - Try again in a moment`,
          };
        }
      } catch (error) {
        console.error('Create ticket error:', error);
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    
        // Check if it's a connection/auth error
        if (errorMessage.includes('401') || errorMessage.includes('auth') || errorMessage.includes('token')) {
          return {
            structuredContent: {
              success: false,
              error: errorMessage,
            },
            content: `## Connection Issue
    
    Your IdeaLift session may have expired.
    
    **Try:** Say "check my connection" to refresh your status.
    
    Your idea is ready - we can create the ticket once reconnected!`,
          };
        }
    
        return {
          structuredContent: {
            success: false,
            error: errorMessage,
          },
          content: `## Something Went Wrong
    
    ${errorMessage}
    
    **Try again** or say "check my connection" to troubleshoot.`,
        };
      }
    }
  • Zod schema (`CreateTicketInput`) defining the expected input structure for the `create_ticket` tool.
    const CreateTicketInput = z.object({
      destination: z.enum(['github', 'jira', 'linear']),
      idea: z.object({
        title: z.string().min(1).max(255),
        summary: z.string(),
        keyPoints: z.array(z.string()).optional(),
        category: z.string().optional(),
        priority: z.enum(['low', 'medium', 'high']).optional(),
      }).optional(), // Now optional - can use draftId instead
      draftId: z.string().optional(), // Reference to saved draft
      destinationConfig: z.object({
        repo: z.string().optional(),
        projectKey: z.string().optional(),
        issueType: z.string().optional(),
        teamId: z.string().optional(),
      }).optional(),
      force: z.boolean().optional(), // Skip duplicate check
    });
  • Tool definition object for `create_ticket`, including its description, input schema, and MCP metadata.
    export const createTicketTool = {
      name: 'create_ticket',
      description: `COMMIT an idea to GitHub, Jira, or Linear. This makes it real.
    
    This is the final step in the IdeaLift flow: Capture → Normalize → COMMIT.
    
    USE this tool when user says:
    - "Commit to GitHub/Jira/Linear"
    - "Commit it", "create this", "file this", "ship it", "make it real"
    - "Push this to [destination]"
    - A number from the commit options (e.g., "1" for GitHub)
    - "1", "2", or "3" after seeing commit options
    
    IMPORTANT: If a normalize_idea was just called, use the draftId from that response.
    If no draftId is available, pass the full idea object from the normalize output.
    NEVER ask the user to re-provide content that was just normalized.
    
    After successful commit, confirm with:
    - The ticket URL (clickable)
    - The ticket ID
    - A clear "COMMITTED" confirmation
    
    This creates a REAL ticket. The user's idea now exists in their system.`,
      inputSchema: {
        type: 'object' as const,
        properties: {
          destination: {
            type: 'string',
            enum: ['github', 'jira', 'linear'],
            description: 'Where to create the ticket (github, jira, or linear)',
          },
          idea: {
            type: 'object',
            properties: {
              title: { type: 'string', description: 'Ticket title' },
              summary: { type: 'string', description: 'Ticket description/body' },
              keyPoints: {
                type: 'array',
                items: { type: 'string' },
                description: 'Acceptance criteria or key points to include',
              },
              category: { type: 'string', description: 'Category (feature, bug, improvement, task)' },
              priority: {
                type: 'string',
                enum: ['low', 'medium', 'high'],
                description: 'Priority level',
              },
            },
            required: ['title', 'summary'],
          },
          destinationConfig: {
            type: 'object',
            properties: {
              repo: { type: 'string', description: 'GitHub repo as owner/repo (e.g., startvest/idealift)' },
              projectKey: { type: 'string', description: 'Jira project key (e.g., IDEA)' },
              issueType: { type: 'string', description: 'Jira issue type (e.g., Story, Bug, Task)' },
              teamId: { type: 'string', description: 'Linear team ID' },
            },
          },
          force: {
            type: 'boolean',
            description: 'Skip duplicate check and create anyway',
          },
          draftId: {
            type: 'string',
            description: 'Draft ID from normalize_idea response. Use this instead of idea if available.',
          },
        },
        required: ['destination'], // idea OR draftId required, validated in handler
      },
      annotations: {
        readOnlyHint: false,    // Creates tickets in external systems and updates draft status
        destructiveHint: false, // Creates new tickets (additive), does NOT delete existing tickets or data
        openWorldHint: true,    // Calls IdeaLift API which then calls GitHub/Jira/Linear APIs
      },
      _meta: {
        'openai/outputTemplate': 'resource://widget/success',
        'openai/widgetAccessible': true,
        'openai/visibility': 'public',
        'openai/toolInvocation/invoking': 'Creating ticket...',
        'openai/toolInvocation/invoked': 'Ticket created!',
      },
    };
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations indicate write-operation (readOnlyHint:false) and external system impact (openWorldHint:true). The description adds valuable context: it confirms creation of 'REAL' external tickets, implies duplicate detection exists (via the 'force' parameter mentioning 'Skip duplicate check'), and describes post-commit behavior (returns URL, ID, confirmation). Minor gap: doesn't mention authentication requirements or failure modes.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

While helpfully structured with capitalized headers ('COMMIT', 'USE', 'IMPORTANT'), the description is overly verbose. The post-success confirmation instructions ('confirm with: The ticket URL...') could belong in system prompts rather than tool description. Some redundancy exists ('This makes it real' vs 'creates a REAL ticket').

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (5 parameters, nested objects, external system interactions, no output schema), the description adequately covers critical gaps. It explains the workflow position, parameter precedence (draftId vs idea), and expected return behavior (URL, ID). Missing: explicit error handling or authentication prerequisites, though `check_auth` exists as a sibling tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 60% schema coverage, the description adds crucial semantic information about the draftId/idea relationship: 'If a normalize_idea was just called, use the draftId... If no draftId is available, pass the full idea object.' This workflow dependency isn't fully captured in the raw schema and helps the agent choose correctly between mutually exclusive approaches.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'COMMIT an idea to GitHub, Jira, or Linear' providing a specific verb and target resource. It clearly distinguishes this from siblings like `normalize_idea` (preparatory step) and `create_idea` (internal creation) by stating this is 'the final step in the IdeaLift flow' and creates a 'REAL ticket' in external systems.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Excellent explicit guidance including: trigger phrases ('Commit it', 'ship it', etc.), workflow context ('final step in the IdeaLift flow: Capture → Normalize → COMMIT'), and prerequisite handling ('If a normalize_idea was just called, use the draftId'). It explicitly names the alternative parameter source and instructs 'NEVER ask the user to re-provide content that was just normalized.'

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Startvest-LLC/idealift-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server