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