get_content_changelog
Track content changes across competitors: detect new, removed, and moved URLs. Filter by competitor or content category to monitor specific updates without full run comparisons.
Instructions
Get detected content changes over time — new URLs, removed URLs, moved URLs. Filter by competitor and/or content category (e.g., blog, docs, tools, landing, caseStudies). Use this instead of comparing full runs when you only need to know what changed. Complements get_content_dashboard which shows the current state. Read-only. Returns paginated JSON array with pagination.hasMore flag.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| projectId | Yes | Project ID (from list_projects) | |
| page | No | Page number (1-indexed, default: 1) | |
| limit | No | Items per page (default: 20, max: 100) | |
| competitorId | No | Filter by competitor ID (from list_competitors) | |
| category | No | Filter by content category |
Implementation Reference
- src/tools.ts:156-186 (schema)Tool definition including name, description, Zod schema for parameters (projectId, pagination, optional competitorId, optional category enum), path, and queryParams for 'get_content_changelog'.
{ name: "get_content_changelog", description: "Get detected content changes over time — new URLs, removed URLs, moved URLs. Filter by competitor and/or content category (e.g., blog, docs, tools, landing, caseStudies). Use this instead of comparing full runs when you only need to know what changed. Complements get_content_dashboard which shows the current state. Read-only. Returns paginated JSON array with pagination.hasMore flag.", parameters: z.object({ projectId: objectId("Project ID (from list_projects)"), ...pagination, competitorId: z .string() .optional() .describe("Filter by competitor ID (from list_competitors)"), category: z .enum([ "blog", "docs", "tools", "landing", "legal", "caseStudies", "comparison", "integrations", "changelog", "webinars", "other", ]) .optional() .describe("Filter by content category"), }), path: (a) => `/v1/projects/${a.projectId}/content/changelog`, queryParams: ["page", "limit", "competitorId", "category"], }, - src/index.ts:14-25 (registration)Generic tool registration loop — iterates over all tools (including get_content_changelog) and registers them via server.tool().
// ── Tools ─────────────────────────────────────────────────── for (const tool of tools) { server.tool(tool.name, tool.description, tool.parameters.shape, async (args: Record<string, any>) => { const path = tool.path(args); const query: Record<string, any> = {}; for (const key of tool.queryParams ?? []) { if (args[key] !== undefined) query[key] = args[key]; } return apiGet(path, Object.keys(query).length ? query : undefined); }); } - src/index.ts:16-24 (handler)Handler function for all tools (including get_content_changelog). Dynamically builds the API path from tool.path(args), collects query parameters from tool.queryParams, and calls apiGet() to make the HTTP GET request to the CompetLab API.
for (const tool of tools) { server.tool(tool.name, tool.description, tool.parameters.shape, async (args: Record<string, any>) => { const path = tool.path(args); const query: Record<string, any> = {}; for (const key of tool.queryParams ?? []) { if (args[key] !== undefined) query[key] = args[key]; } return apiGet(path, Object.keys(query).length ? query : undefined); }); - src/api-client.ts:1-58 (helper)API client helper — apiGet() function used by all tool handlers. Builds the full URL, adds query params, calls fetch with CL-API-Key header, and returns the response text wrapped in the MCP content format.
const API_BASE = "https://api.competlab.com"; export async function apiGet( path: string, query?: Record<string, string | number>, ): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: true }> { const apiKey = process.env.COMPETLAB_API_KEY; if (!apiKey) { return { content: [ { type: "text", text: JSON.stringify({ error: "api_key_missing", message: "COMPETLAB_API_KEY environment variable is not set", }), }, ], isError: true, }; } const url = new URL(`${API_BASE}${path}`); if (query) { for (const [k, v] of Object.entries(query)) { if (v !== undefined) url.searchParams.set(k, String(v)); } } try { const res = await fetch(url, { headers: { "CL-API-Key": apiKey }, }); const body = await res.text(); if (!res.ok) { return { content: [{ type: "text", text: body }], isError: true }; } return { content: [{ type: "text", text: body }] }; } catch (err) { return { content: [ { type: "text", text: JSON.stringify({ error: "api_unreachable", message: err instanceof Error ? err.message : "Failed to reach CompetLab API", status: 503, }), }, ], isError: true, }; } }