get_positioning_run_detail
Retrieve full competitor-by-competitor positioning data for a historical run. Explore messaging changes over time by comparing past and current data. Requires project ID and run ID from history.
Instructions
Get full competitor-by-competitor Positioning data for a specific historical run. Returns the same data structure as get_positioning_dashboard but for a past point in time. Use this to investigate how competitor messaging evolved between runs. Requires runId from get_positioning_history. Read-only. Returns JSON object.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| projectId | Yes | Project ID (from list_projects) | |
| runId | Yes | Run ID (from get_positioning_history) |
Implementation Reference
- src/tools.ts:210-218 (schema)Schema definition for get_positioning_run_detail tool: defines name, description, parameters (projectId, runId as ObjectId strings), and the API path pattern.
name: "get_positioning_run_detail", description: "Get full competitor-by-competitor Positioning data for a specific historical run. Returns the same data structure as get_positioning_dashboard but for a past point in time. Use this to investigate how competitor messaging evolved between runs. Requires runId from get_positioning_history. Read-only. Returns JSON object.", parameters: z.object({ projectId: objectId("Project ID (from list_projects)"), runId: objectId("Run ID (from get_positioning_history)"), }), path: (a) => `/v1/projects/${a.projectId}/positioning/history/${a.runId}`, }, - src/index.ts:16-25 (handler)Generic handler that registers all tools (including get_positioning_run_detail) via a loop. The handler constructs the API path from the tool definition, collects query params, and calls apiGet to make the HTTP request.
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:3-50 (helper)Helper function apiGet: makes authenticated GET requests to the CompetLab API, handling errors and returning MCP-compatible content objects. Used by all tools including get_positioning_run_detail.
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", - src/index.ts:16-25 (registration)Registration of get_positioning_run_detail on the MCP server via the generic loop that iterates over all tool definitions.
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); }); }