MCP server for tracking brand visibility and mentions across AI engine search results. Manages prompts, results, and brand tracking with extraction and analytics.
geo-tracker demonstrates solid tool design with 9 well-named, verb-prefixed tools and comprehensive input schemas using Zod validation. All tools have descriptions (avg 120 chars) and typed parameters with enums where appropriate. However, output schemas are not explicitly documented in the MCP handler registration, forcing LLMs to infer response structure. Error handling is present but lacks recovery guidance. Tool composition is clean (single responsibility), and parameter naming follows conventions (promptId, brandId, resultId). Missing: per-tool output schema documentation, actionable error messages, and tool annotations (readOnlyHint/destructiveHint).
Create a prompt to track. It will run runsPerDay times per day against the selected AI engines.
How often each tracked brand was named in answers, and how often its own pages were cited, over the last N days. Counts every completed run as a denominator, so a brand that was never named reports 0 rather than going missing.
Fetch a single result row including the full raw engine response payload.
Query stored run results (without the raw response payload — use get_result for that). Filter by prompt, engine or status.
List the brands being looked for in answers, with their aliases and domains.
List all configured prompts with their engines, schedule and last run time.
Output schemas not documented in MCP tool registration. LLMs cannot infer response structure (fields, types, pagination). Responses are returned as text via text() helper without schema declaration.
Tool annotations missing. No readOnlyHint, destructiveHint, or idempotentHint declared. LLMs cannot distinguish safe reads from destructive writes (e.g., untrack_brand deletes data but has no annotation).
Error responses lack recovery guidance. HttpError thrown but no actionable next steps provided to LLM (e.g., 'Brand not found' should suggest 'Call list_brands() to see available brands').
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | B | 72 | 2026-07-28+ | v2 |
Submit a prompt to its engines immediately. Returns pending task ids; results arrive asynchronously (check get_results shortly after).
Start looking for a brand in answers. Aliases are extra spellings that count as the same brand; domains decide when a link counts as citing it. Every answer already stored is re-scored against the new brand, so its history starts full rather than empty.
Delete a brand and every mention row derived for it. The raw answers are untouched.
Async task handling not documented. run_prompt returns 'pending task ids' but get_results polling behavior and latency expectations are not explained in descriptions.
Parameter 'optional' field used in schema but not standard JSON Schema. Should use 'required' array at schema root level for clarity.