Competitive intelligence for B2B SaaS — monitor competitors across tech stack, content, positioning, pricing, AI visibility (how ChatGPT, Claude, Gemini, Perplexity and Google AI Overviews name and recommend your brand against competitors), and AI sources (the pages Perplexity and Google AI Overviews read when they answer your buyers' questions, and whether your brand is on them)
CompetLab MCP server demonstrates solid definition quality with well-structured tool schemas, comprehensive descriptions, and proper parameter validation. All 5 tools have explicit Zod schemas with regex validation for ObjectIds. Descriptions are detailed and domain-specific (194 - 2000+ chars, well above the 10 - 1024 baseline). Tool names follow verb_noun convention (list_, get_). However, output schemas are not documented in the source, only input schemas are visible. Error handling guidance is minimal. Tool annotations (readOnlyHint) are present but limited. The server correctly handles type coercion (booleanish, zeroBasedIndex unions) to match hosted API behavior, showing production maturity.
Get competitor details including monitored pages (homepage URL, pricing page URL). Use competitorId values from list_competitors.
Get project details including per-dimension monitoring freshness (techTrust, content, positioning, pricing, aiVisibility), AI monitoring prompts, and overall status. Use this to check when each dimension last produced data. For aiVisibility that timestamp is the last check that published a measurement — a cycle that came back short is abandoned and never moves it, so neither an unchanged timestamp nor null proves nothing ran; get_ai_visibility_dashboard reports that case in latestCheckDataAvailable, and get_ai_visibility_trend reports it under events.incompleteCycles when nothing has ever published.
Get the latest Tech & Trust Profile for all competitors. Returns security headers (grade A-F, HSTS, CSP, X-Frame-Options), trust signals, technology stack, AI access, DNS infrastructure. AI ACCESS answers two independent questions per competitor, on `aiAccess`. ANSWER ACCESS: which AI assistants may obtain their content, one verdict per assistant, each naming the crawlers that decided it. TRAINING ACCESS: which model operators may train on it. They are separate facts with opposite valence. AS A RULE, blocking a training crawler costs NO assistant visibility and is a legitimate content decision — do not report it as a problem, a gap, or something to undo, and never total the two into one score. THE RULE HAS ONE EXCEPTION AND YOU MUST CHECK FOR IT BEFORE SUPPRESSING ANYTHING: a few tokens govern training AND an assistant at once, `Google-Extended` being the documented case, so where a crawler under `modelTrainingAccess[].decidedByCrawlers` ALSO appears under `assistantAccess[].decidedByCrawlers` — match on `userAgentToken` — that block does carry a visibility consequence and must be reported. Suppressing it is how a site that closed itself to Gemini answers while believing it had only opted out of training goes unnoticed. There is no aggregate boolean and no stored count: derive any total from the length of the array you are quoting, so the number and the names cannot disagree. Nothing here measures whether an assistant actually mentions or cites anyone — it measures permission to obtain the content, which is a different thing and must be worded as such. EVERY VERDICT SHIPS ITS OWN SENTENCES on `explanations`, generated from our crawler catalog. Render them; do not paraphrase them into your own claim. They already encode what blocking each crawler costs, and how firmly a compliance claim may be worded — an operator's own words are quoted, a measurement is cited with its limits, a dispute is reported as a dispute, and `undocumented` means nobody has published either way and NEVER means the crawler ignores robots.txt. `aiAccess.measurement.explanations` CARRIES THE SAME KIND OF SENTENCES ABOUT THE MEASUREMENT ITSELF. Read it whenever `measurement.status` is not `measured`, and quote it rather than writing your own. On `could_not_measure` it is the ONLY prose there is, because the verdict arrays and their per-verdict `explanations` are absent together. On `measured_no_policy_found` the verdicts are PRESENT and complete — a site with no robots.txt allows every crawler under the standard, which is a measured result — and these sentences say that the openness follows from the standard's default rather than from anything the site wrote. It is absent, never empty, when the file was read and the verdicts are the whole answer. EACH DECIDING CRAWLER SAYS WHICH RULE DECIDED IT. `decidedByRuleFor` is `names_this_crawler` when a group naming it decided the outcome — a policy somebody chose — or `wildcard_catch_all` when the site's `User-agent: *` rule swept it up, which is frequently a rule written before that crawler existed. `decidedByDirective` and `decidedByLineNumber` give the exact line. That distinction is the actionable part: the two states look identical in a verdict and require editing different lines. `rulesThatMayNotWork` names rules whose effect is uncertain BECAUSE OF THE CRAWLER'S OPERATOR — a published carve-out, or a measured refusal to honour disallows — never because of anything the site did wrong. It never suggests replacing a token, and you must not either: telling a site its user-agent is obsolete requires a registry of live tokens that nobody publishes. `rulesWithUnintendedScope` is its COUNTERPART and reads the opposite way: those rules worked exactly as written and simply did not reach the crawler named beside them, because the standard makes a group naming a crawler REPLACE the `User-agent: *` group rather than add to it. So writing `User-agent: GPTBot` / `Allow: /` under a `*` group that closes three sections leaves GPTBot permitted in all three. Report the mechanism and stop: whether the exemption was intended is not something we measure, and privileging AI crawlers over other traffic is a legitimate strategy — so never call it an error. It is still worth surfacing, because the author may not know the standard works this way, and for a TRAINING token the exemption does cost the content protection they thought they had. `affectedRules` is a FLOOR — only content-area directives are reported, not hygiene paths — and it belongs to the ONE crawler in its own finding, since the lists differ between crawlers in the same file. Never quote one finding's rules against another's name. `additionalObservations` carries our own coverage of that file, stated once with the universe it is drawn from; it is a disclosure of OUR limits, not a finding about them. Trust signals are 26 specific things we look for on a HOMEPAGE, in five categories — compliance (6: SOC 2, ISO 27001, HIPAA, GDPR, CCPA, LGPD), reviews (6: G2, Capterra, Trustpilot, Gartner, TrustRadius, GetApp), social proof (5: Customer Logos, Customer Count, Case Studies, Money Back Guarantee, Free Trial), certifications (8: PCI DSS, FedRAMP, ISO 9001, ISO 14001, SOC 1, SOC 3, NIST, HITRUST) and security (1: Security.txt).
Output schemas not documented. Tool descriptions explain what data is returned (security headers, trust signals, AI access verdicts) but the response structure is not formally specified. LLMs cannot plan downstream operations or extract fields without seeing the schema.
No error handling guidance. Tools declare no error responses, recovery paths, or actionable error messages. If an invalid projectId is passed, the LLM receives only an HTTP error with no hint to call list_projects() first.
Pagination not exposed in tool definitions. list_projects and list_competitors likely return paginated results (common for competitive intelligence APIs), but no page/limit parameters are visible in the schemas. This forces agents to retrieve all results at once, risking context window exhaustion.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 68 | 2026-07-28+ | v2 |
List all competitors being monitored for a project. Includes the user's own domain (marked isOwn: true) for self-analysis comparison. Each row carries id, domain, isOwn, preparationStatus (whether the competitor's data has been prepared for monitoring) and createdAt. There is no display name on this list — the domain is the identity, and the brand name the AI models use for it lives on the AI Visibility market map.
List all accessible projects with status, competitor count, and last monitored timestamp. This is the starting point — use it to discover available projectId values for other tools. (Scope depends on the key: the customer MCP returns one organization's projects; the Atlas control-plane MCP returns every project across all organizations.)
Tool annotations incomplete. Only readOnlyHint is used; destructiveHint and idempotentHint are absent. This limits agent reasoning about retry safety and side effects.
get_tech_trust_dashboard description is exceptionally long (2000+ chars) and contains embedded instructions for LLM interpretation (e.g., 'do not paraphrase', 'render them', 'quote it'). While domain-accurate, this violates the 10 - 1024 char baseline and risks token waste. Consider splitting into a separate guide or API documentation.