Skip to main content
Glama
Jameswlepage

WordPress Trac MCP Server

by Jameswlepage

getChangeset

Retrieve detailed information about a WordPress code changeset, including commit message, author, and diff content for specific SVN revisions.

Instructions

Get information about a specific WordPress code changeset/commit including commit message, author, and diff.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
revisionYesSVN revision number (e.g., 58504)
includeDiffNoInclude diff content (default: true)
diffLimitNoMaximum characters of diff to return (default: 2000, max: 10000)

Implementation Reference

  • Input schema for the getChangeset tool, defining parameters: revision (required), includeDiff, diffLimit.
    {
      name: "getChangeset",
      description: "Get information about a specific WordPress code changeset/commit including commit message, author, and diff.",
      inputSchema: {
        type: "object",
        properties: {
          revision: {
            type: "number",
            description: "SVN revision number (e.g., 58504)",
          },
          includeDiff: {
            type: "boolean",
            description: "Include diff content (default: true)",
            default: true,
          },
          diffLimit: {
            type: "number",
            description: "Maximum characters of diff to return (default: 2000, max: 10000)",
            default: 2000,
          },
        },
        required: ["revision"],
      },
    },
  • Core handler logic for getChangeset: fetches changeset HTML from Trac, parses metadata (author, date, message, files) using regex, optionally fetches and truncates unified diff.
    case "getChangeset": {
      const { revision, includeDiff = true, diffLimit = 2000 } = args;
      
      try {
        const changesetUrl = `https://core.trac.wordpress.org/changeset/${revision}`;
        
        // Fetch changeset page
        const response = await fetch(changesetUrl, {
          headers: {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
          }
        });
    
        if (!response.ok) {
          throw new Error(`Changeset ${revision} not found`);
        }
    
        const html = await response.text();
        
        // Parse changeset information from HTML with improved patterns
        const messageMatch = html.match(/<dd class="message[^"]*"[^>]*>\s*<p[^>]*>(.*?)<\/p>/s) || 
                            html.match(/<dd class="message[^"]*"[^>]*>(.*?)<\/dd>/s) ||
                            html.match(/<div class="message"[^>]*>\s*<p[^>]*>(.*?)<\/p>/s) || 
                            html.match(/<div class="message"[^>]*>(.*?)<\/div>/s);
        const authorMatch = html.match(/<dd class="author"[^>]*><span class="trac-author"[^>]*>(.*?)<\/span><\/dd>/s) ||
                           html.match(/<dt class="property author">Author:<\/dt>\s*<dd class="author">(.*?)<\/dd>/s) ||
                           html.match(/<dt>Author:<\/dt>\s*<dd>(.*?)<\/dd>/s);
        const dateMatch = html.match(/<dd class="date"[^>]*>(.*?)<\/dd>/s) ||
                         html.match(/<dt class="property date">Date:<\/dt>\s*<dd class="date">(.*?)<\/dd>/s) ||
                         html.match(/<dt>Date:<\/dt>\s*<dd>(.*?)<\/dd>/s);
        
        const changeset = {
          revision,
          author: authorMatch?.[1] ? authorMatch[1].replace(/<[^>]*>/g, '').trim() : '',
          date: dateMatch?.[1] ? dateMatch[1].replace(/<[^>]*>/g, '').trim() : '',
          message: messageMatch?.[1] ? messageMatch[1].replace(/<[^>]*>/g, '').trim() : '',
          files: [] as string[],
          diff: '',
        };
    
        // Extract file list with improved patterns
        const fileMatches = html.match(/<h2[^>]*>Files:<\/h2>([\s\S]*?)<\/div>/) ||
                           html.match(/<div class="files"[^>]*>([\s\S]*?)<\/div>/) ||
                           html.match(/<div[^>]*class="[^"]*files[^"]*"[^>]*>([\s\S]*?)<\/div>/);
        if (fileMatches?.[1]) {
          const fileListHtml = fileMatches[1];
          const filePathMatches = fileListHtml.match(/<a[^>]*href="[^"]*\/browser\/[^"]*"[^>]*>(.*?)<\/a>/g) ||
                                 fileListHtml.match(/<a[^>]*href="[^"]*"[^>]*>(.*?)<\/a>/g) ||
                                 fileListHtml.match(/<li[^>]*>(.*?)<\/li>/g);
          if (filePathMatches) {
            changeset.files = filePathMatches
              .map(match => match.replace(/<[^>]*>/g, '').trim())
              .filter(path => path && !path.includes('(') && !path.includes('modified') && !path.includes('added') && !path.includes('deleted'))
              .slice(0, 20); // Limit to first 20 files
          }
        }
    
        // Get diff if requested
        if (includeDiff) {
          try {
            const diffUrl = `${changesetUrl}?format=diff`;
            const diffResponse = await fetch(diffUrl, {
              headers: {
                'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
              }
            });
    
            if (diffResponse.ok) {
              let diffText = await diffResponse.text();
              const maxDiffLength = Math.min(diffLimit, 10000);
              if (diffText.length > maxDiffLength) {
                diffText = diffText.substring(0, maxDiffLength) + '\n... [diff truncated] ...';
              }
              changeset.diff = diffText;
            }
          } catch (error) {
            console.warn('Failed to load diff:', error);
          }
        }
    
        result = {
          id: revision.toString(),
          title: `r${revision}: ${changeset.message}`,
          text: `Changeset r${revision}\nAuthor: ${changeset.author}\nDate: ${changeset.date}\n\nMessage:\n${changeset.message}\n\nFiles changed: ${changeset.files.length}\n${changeset.files.slice(0, 10).join('\n')}${changeset.files.length > 10 ? '\n...' : ''}\n\n${changeset.diff ? `Diff:\n${changeset.diff}` : 'No diff available'}`,
          url: changesetUrl,
          metadata: {
            changeset,
            totalFiles: changeset.files.length,
          },
        };
      } catch (error) {
        result = {
          id: revision.toString(),
          title: `Error loading changeset ${revision}`,
          text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
          url: `https://core.trac.wordpress.org/changeset/${revision}`,
          metadata: { error: true },
        };
      }
      break;
  • Helper function getChangesetForChatGPT: similar to main handler but simplified for ChatGPT 'search' and 'fetch' tools, with caching.
    async function getChangesetForChatGPT(revision: number, includeDiff: boolean) {
      try {
        const changesetUrl = `https://core.trac.wordpress.org/changeset/${revision}`;
        
        const response = await fetch(changesetUrl, {
          headers: {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
          }
        });
    
        if (!response.ok) {
          throw new Error(`Changeset ${revision} not found`);
        }
    
        const html = await response.text();
        
        const messageMatch = html.match(/<dd class="message[^"]*"[^>]*>\s*<p[^>]*>(.*?)<\/p>/s) || 
                            html.match(/<dd class="message[^"]*"[^>]*>(.*?)<\/dd>/s);
        const authorMatch = html.match(/<dd class="author"[^>]*><span class="trac-author"[^>]*>(.*?)<\/span><\/dd>/s) ||
                           html.match(/<dt class="property author">Author:<\/dt>\s*<dd class="author">(.*?)<\/dd>/s);
        const dateMatch = html.match(/<dd class="date"[^>]*>(.*?)<\/dd>/s) ||
                         html.match(/<dt class="property date">Date:<\/dt>\s*<dd class="date">(.*?)<\/dd>/s);
        
        const changeset = {
          revision,
          author: authorMatch?.[1] ? authorMatch[1].replace(/<[^>]*>/g, '').trim() : '',
          date: dateMatch?.[1] ? dateMatch[1].replace(/<[^>]*>/g, '').trim() : '',
          message: messageMatch?.[1] ? messageMatch[1].replace(/<[^>]*>/g, '').trim() : '',
          diff: '',
        };
    
        // Get diff if requested
        if (includeDiff) {
          try {
            const diffUrl = `${changesetUrl}?format=diff`;
            const diffResponse = await fetch(diffUrl, {
              headers: {
                'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
              }
            });
    
            if (diffResponse.ok) {
              let diffText = await diffResponse.text();
              const maxDiffLength = 2000;
              if (diffText.length > maxDiffLength) {
                diffText = diffText.substring(0, maxDiffLength) + '\n... [diff truncated] ...';
              }
              changeset.diff = diffText;
            }
          } catch (error) {
            console.warn('Failed to load diff:', error);
          }
        }
    
        // Cache the changeset
        chatgptCache.set(`r${revision}`, changeset);
    
        const diffText = changeset.diff ? `\n\nDiff:\n${changeset.diff}` : '';
    
        return {
          id: `r${revision}`,
          title: `r${revision}: ${changeset.message}`,
          text: `Changeset r${revision}\nAuthor: ${changeset.author}\nDate: ${changeset.date}\n\nMessage:\n${changeset.message}${diffText}`,
          url: changesetUrl,
          metadata: { changeset },
        };
      } catch (error) {
        return {
          id: `r${revision}`,
          title: `Error loading changeset ${revision}`,
          text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
          url: `https://core.trac.wordpress.org/changeset/${revision}`,
          metadata: { error: true },
        };
      }
    }
  • src/index.ts:40-156 (registration)
    Tool registration in tools/list response, listing getChangeset among available tools with its schema.
    return {
      jsonrpc: "2.0",
      id,
      result: {
        tools: [
          {
            name: "searchTickets",
            description: "Search for WordPress Trac tickets by keyword or filter expression. Returns ticket summaries with basic info.",
            inputSchema: {
              type: "object",
              properties: {
                query: {
                  type: "string",
                  description: "Search query for tickets (keywords or filter expressions like 'summary~=keyword')",
                },
                limit: {
                  type: "number",
                  description: "Maximum number of results to return (default: 10, max: 50)",
                  default: 10,
                },
                status: {
                  type: "string",
                  description: "Filter by ticket status (e.g., 'open', 'closed', 'new')",
                },
                component: {
                  type: "string", 
                  description: "Filter by component name (e.g., 'Administration', 'Posts, Post Types')",
                },
              },
              required: ["query"],
            },
          },
          {
            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"],
            },
          },
          {
            name: "getChangeset",
            description: "Get information about a specific WordPress code changeset/commit including commit message, author, and diff.",
            inputSchema: {
              type: "object",
              properties: {
                revision: {
                  type: "number",
                  description: "SVN revision number (e.g., 58504)",
                },
                includeDiff: {
                  type: "boolean",
                  description: "Include diff content (default: true)",
                  default: true,
                },
                diffLimit: {
                  type: "number",
                  description: "Maximum characters of diff to return (default: 2000, max: 10000)",
                  default: 2000,
                },
              },
              required: ["revision"],
            },
          },
          {
            name: "getTimeline",
            description: "Get recent activity from WordPress Trac timeline including recent tickets, commits, and other events.",
            inputSchema: {
              type: "object",
              properties: {
                days: {
                  type: "number",
                  description: "Number of days to look back (default: 7, max: 30)",
                  default: 7,
                },
                limit: {
                  type: "number",
                  description: "Maximum number of events to return (default: 20, max: 100)",
                  default: 20,
                },
              },
            },
          },
          {
            name: "getTracInfo",
            description: "Get WordPress Trac metadata like components, milestones, priorities, and severities.",
            inputSchema: {
              type: "object",
              properties: {
                type: {
                  type: "string",
                  enum: ["components", "milestones", "priorities", "severities"],
                  description: "Type of Trac information to retrieve",
                },
              },
              required: ["type"],
            },
          },
        ],
      },
    };

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