Skip to main content
Glama
Jameswlepage

WordPress Trac MCP Server

by Jameswlepage

getTicket

Retrieve detailed WordPress Trac ticket information including descriptions, comments, and metadata to track development issues and discussions.

Instructions

Get detailed information about a specific WordPress Trac ticket including description, comments, and metadata.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
idYesTrac ticket ID number
includeCommentsNoInclude ticket comments and discussion (default: true)
commentLimitNoMaximum number of comments to return (default: 10, max: 50)

Implementation Reference

  • src/index.ts:72-96 (registration)
    Registration of the 'getTicket' tool in the tools/list MCP method response, including its input schema definition.
    {
      name: "getTicket",
      description: "Get detailed information about a specific WordPress Trac ticket including description, comments, and metadata.",
      inputSchema: {
        type: "object",
        properties: {
          id: {
            type: "number",
            description: "Trac ticket ID number",
          },
          includeComments: {
            type: "boolean",
            description: "Include ticket comments and discussion (default: true)",
            default: true,
          },
          commentLimit: {
            type: "number",
            description: "Maximum number of comments to return (default: 10, max: 50)",
            default: 10,
          },
        },
        required: ["id"],
      },
    },
    {
  • Primary handler implementation for the 'getTicket' tool within the handleMcpRequest function's tools/call switch statement. Fetches ticket data using Trac's CSV query API, parses the response, extracts metadata, handles comments placeholder, and formats the output.
    case "getTicket": {
      const { id, includeComments = true } = args;
      
      try {
        // Use search approach since CSV parsing is problematic
        const searchUrl = new URL('https://core.trac.wordpress.org/query');
        searchUrl.searchParams.set('format', 'csv');
        searchUrl.searchParams.set('id', id.toString());
        searchUrl.searchParams.set('max', '1');
        
        const response = await fetch(searchUrl.toString(), {
          headers: {
            'User-Agent': 'Mozilla/5.0 (compatible; WordPress-Trac-MCP-Server/1.0)',
            'Accept': 'text/csv,text/plain,*/*',
          }
        });
        
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        const csvData = await response.text();
        
        // Parse CSV data similar to searchTickets
        const lines = csvData.replace(/^\uFEFF/, '').trim().split(/\r?\n/);
        if (lines.length < 2) {
          throw new Error(`Ticket ${id} not found`);
        }
        
        // Parse each line like in searchTickets
        for (let i = 1; i < lines.length; i++) {
          const line = lines[i]?.trim();
          if (!line) continue;
          
          // Better CSV parsing - handle quoted fields properly
          const values = [];
          let currentField = '';
          let inQuotes = false;
          let escapeNext = false;
          
          for (let j = 0; j < line.length; j++) {
            const char = line[j];
            
            if (escapeNext) {
              currentField += char;
              escapeNext = false;
              continue;
            }
            
            if (char === '\\') {
              escapeNext = true;
              continue;
            }
            
            if (char === '"') {
              if (inQuotes) {
                // Check if this is an escaped quote
                if (j + 1 < line.length && line[j + 1] === '"') {
                  currentField += '"';
                  j++; // Skip the next quote
                } else {
                  inQuotes = false;
                }
              } else {
                inQuotes = true;
              }
            } else if (char === ',' && !inQuotes) {
              values.push(currentField.trim());
              currentField = '';
            } else {
              currentField += char;
            }
          }
          values.push(currentField.trim());
          
          if (values.length >= 2 && values[0] && !isNaN(parseInt(values[0]))) {
            const ticketId = parseInt(values[0]);
            if (ticketId === id) {
              // Map fields based on actual headers from search query
              // Headers: id,Summary,Owner,Type,Status,Priority,Milestone
              const ticket = {
                id: parseInt(values[0]),
                summary: values[1] || '',
                owner: values[2] || '',
                type: values[3] || '',
                status: values[4] || '',
                priority: values[5] || '',
                milestone: values[6] || '',
                reporter: '', // Not available in search query
                description: 'Full description not available in search query. Visit the ticket URL for complete details.',
                component: '', // Not available in search query
                version: '',
                severity: '',
                resolution: '',
                keywords: '',
                cc: '',
                focuses: '',
              };
    
              // Note: Comments are not available through the CSV API
              let comments: any[] = [];
              
              if (includeComments) {
                comments = [{
                  author: 'system',
                  timestamp: new Date().toISOString(),
                  comment: 'Comment history not available through CSV API. Visit the ticket URL for full discussion.',
                }];
              }
    
              result = {
                id: id,
                title: `#${id}: ${ticket.summary}`,
                text: `Ticket #${id}: ${ticket.summary}\n\nStatus: ${ticket.status}\nComponent: ${ticket.component}\nPriority: ${ticket.priority}\nType: ${ticket.type}\nReporter: ${ticket.reporter}\nOwner: ${ticket.owner}\nMilestone: ${ticket.milestone}\nVersion: ${ticket.version}\nKeywords: ${ticket.keywords}\n\nDescription:\n${ticket.description}\n\nFor full discussion and comments, visit: https://core.trac.wordpress.org/ticket/${id}`,
                url: `https://core.trac.wordpress.org/ticket/${id}`,
                metadata: {
                  ticket,
                  comments,
                  totalComments: comments.length,
                },
              };
              break; // Found the ticket, exit the loop
            }
          }
        }
        
        // If we didn't find the ticket, result will be undefined
        if (!result) {
          throw new Error(`Ticket ${id} not found`);
        }
      } catch (error) {
        result = {
          id: id,
          title: `Error loading ticket ${id}`,
          text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
          url: `https://core.trac.wordpress.org/ticket/${id}`,
          metadata: { error: true },
        };
      }
      break;
    }
  • Helper function getTicketForChatGPT used in the ChatGPT-specific MCP handler for fetching and formatting ticket data, similar logic to main handler but simplified for ChatGPT's search/fetch tools.
    async function getTicketForChatGPT(ticketId: number, includeComments: boolean) {
      try {
        const searchUrl = new URL('https://core.trac.wordpress.org/query');
        searchUrl.searchParams.set('format', 'csv');
        searchUrl.searchParams.set('id', ticketId.toString());
        searchUrl.searchParams.set('max', '1');
        
        const response = await fetch(searchUrl.toString(), {
          headers: {
            'User-Agent': 'Mozilla/5.0 (compatible; WordPress-Trac-MCP-Server/1.0)',
            'Accept': 'text/csv,text/plain,*/*',
          }
        });
        
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        const csvData = await response.text();
        const lines = csvData.replace(/^\uFEFF/, '').trim().split(/\r?\n/);
        if (lines.length < 2) {
          throw new Error(`Ticket ${ticketId} not found`);
        }
        
        const values = parseCSVLine(lines[1] || '');
        if (values.length >= 2 && values[0] && parseInt(values[0]) === ticketId) {
          const ticket = {
            id: parseInt(values[0]),
            summary: values[1] || '',
            owner: values[2] || '',
            type: values[3] || '',
            status: values[4] || '',
            priority: values[5] || '',
            milestone: values[6] || '',
          };
    
          // Cache the ticket
          chatgptCache.set(ticketId.toString(), ticket);
    
          const commentNote = includeComments 
            ? "\n\nNote: Full comments and description available on the ticket page."
            : "";
    
          return {
            id: ticketId.toString(),
            title: `#${ticketId}: ${ticket.summary}`,
            text: `Ticket #${ticketId}: ${ticket.summary}\n\nStatus: ${ticket.status}\nType: ${ticket.type}\nPriority: ${ticket.priority}\nOwner: ${ticket.owner}\nMilestone: ${ticket.milestone}${commentNote}`,
            url: `https://core.trac.wordpress.org/ticket/${ticketId}`,
            metadata: { ticket },
          };
        } else {
          throw new Error(`Ticket ${ticketId} not found`);
        }
      } catch (error) {
        return {
          id: ticketId.toString(),
          title: `Error loading ticket ${ticketId}`,
          text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
          url: `https://core.trac.wordpress.org/ticket/${ticketId}`,
          metadata: { error: true },
        };
      }
    }
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. While it mentions what information is returned (description, comments, metadata), it doesn't cover important behavioral aspects like whether this is a read-only operation (implied but not stated), error handling for invalid IDs, rate limits, authentication requirements, or response format. For a tool with no annotation coverage, this leaves significant gaps.

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

Conciseness4/5

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

The description is a single, well-structured sentence that efficiently communicates the core functionality. It's appropriately sized for a simple retrieval tool and front-loads the essential information. There's no wasted language, though it could potentially be more comprehensive given the lack of annotations.

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

Completeness2/5

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

Given the tool has no annotations and no output schema, the description is incomplete. It doesn't explain what the return value looks like (structure, format), error conditions, or important behavioral constraints. For a tool that retrieves potentially complex ticket data with comments and metadata, more context is needed to help an agent use it effectively.

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

Parameters3/5

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

The description doesn't add any parameter-specific information beyond what's already in the schema. Since schema description coverage is 100%, all parameters (id, includeComments, commentLimit) are fully documented in the schema itself. The description mentions 'including description, comments, and metadata' which aligns with the parameters but doesn't provide additional semantic context. This meets the baseline for high schema coverage.

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

Purpose4/5

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

The description clearly states the tool's purpose: 'Get detailed information about a specific WordPress Trac ticket including description, comments, and metadata.' It specifies the verb ('Get'), resource ('WordPress Trac ticket'), and scope of information returned. However, it doesn't explicitly differentiate from sibling tools like 'getTracInfo' or 'searchTickets' which might also retrieve ticket information, so it doesn't reach the highest score.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention when to choose 'getTicket' over 'searchTickets' for finding tickets, or how it differs from 'getTracInfo' which might provide broader Trac information. There's no context about prerequisites, typical use cases, or exclusions.

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/Jameswlepage/trac-mcp'

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