Skip to main content
Glama

Analytics Legends — SAP AI & Analytics MCP Server

Search the SAP AI & analytics firm directory

search_firms
Read-onlyIdempotent

Search the published Analytics Legends directory of SAP AI & analytics service providers — placement agencies, Big-4 and ESN practices, SAP vendors, platforms and community groups — by country, region (EMEA · AMER · LATAM · APAC · AFRICA), kind, declared SAP module and free text. Returns name, HQ country/city, website, careers URL and a one-line editorial claim. SAP END-CUSTOMER companies are NOT in this directory: they are a separate paid dataset, excluded here by the is_client FLAG — not by the client_enterprise kind code. The two are different columns, and where a row's flag and its kind label disagree in the SSOT it is the flag that decides what this tool serves, so read the flag's meaning into the answer and not the label's. PAGINATED WITH A KEY (a free one is enough): the whole matched set is reachable — pass _meta.next_cursor back as cursor with the same filters until it is null. Without a key: first 10 rows only, no cursor. When query is set, rows are ordered by how well the NAME matches it (exact, then prefix, then substring), and rows matching only the description come last; without query the order is the directory's own quality ranking.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
kindNoRestrict to one organisation kind. This list is the vocabulary the corpus holds today, not a frontier — call list_firm_kinds for the live one. A malformed code is refused; a well-formed code the corpus does not hold returns no rows. Neither case is silently ignored, and neither widens the result.
limitNoMax rows (hard cap 50).
queryNoFree-text filter, case-insensitive. EVERY word must appear in the record (substring per word, any order), so a natural-language phrase narrows the answer instead of having to match verbatim.
cursorNoOpaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor.
moduleNoRestrict to organisations with a DECLARED link to one SAP module/product code (UPPERCASE snake_case, e.g. DATASPHERE, BDC, SAC, BW4HANA, S4HANA, JOULE — case-insensitive on input). The declared links are structured data, far more selective than free text: `count_firms_by {by:"module"}` gives the live vocabulary with counts. A minority of the directory declares any module at all, so this filter finds the DECLARED specialists — absence from the result means no declared link, never that the firm does not work on the module.
regionNoRegion code of the HQ country — the platform's six-value geography: EMEA (Europe AND the Middle East), AMER (North America), LATAM, APAC, AFRICA (its own region, not folded into EMEA), GLOBAL (country unknown). Case-insensitive on input; anything else is refused.
countryNoISO-3166-1 alpha-2 country code, e.g. DE, FR, CH.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • addedInput schema / properties / region
      Added value: +{
      +  "description": "Region code of the HQ country — the platform's six-value geography: EMEA (Europe AND the Middle East), AMER (North America), LATAM, APAC, AFRICA (its own region, not folded into EMEA), GLOBAL (country unknown). Case-insensitive on input; anything else is refused.",
      +  "enum": [
      +    "EMEA",
      +    "AMER",
      +    "LATAM",
      +    "APAC",
      +    "AFRICA",
      +    "GLOBAL"
      +  ],
      +  "type": "string"
      +}
  2. Changed1 schema field changed
    • changedInput schema / properties / kind / enum
      Previous value: -[
      -  "agency_placement",
      -  "professional_services_big4",
      -  "professional_services_esn",
      -  "vendor_sap",
      -  "vendor_partner",
      -  "platform_marketplace",
      -  "community_group",
      -  "client_enterprise",
      -  "other"
      -]New value: +[
      +  "agency_placement",
      +  "professional_services_big4",
      +  "professional_services_esn",
      +  "vendor_sap",
      +  "vendor_partner",
      +  "platform_marketplace",
      +  "community_group",
      +  "training_institution",
      +  "client_enterprise",
      +  "other"
      +]
  3. Changed1 schema field changed
    • changedInput schema / properties / query / description
      Previous value: -"Free-text filter, matched case-insensitively."New value: +"Free-text filter, case-insensitive. EVERY word must appear in the record (substring per word, any order), so a natural-language phrase narrows the answer instead of having to match verbatim."
  4. Changed1 schema field changed
    • changedInput schema / properties / cursor / description
      Previous value: -"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place."New value: +"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor."
  5. Changed8 schema fields changed
    • addedOutput schema / properties / rows / items / properties / claim
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / founded_year
      Added value: +{
      +  "type": [
      +    "integer",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / hq_city
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / jobs_url
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / last_verified_at
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / region
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / sap_signal_band
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / rows / items / properties / size_bracket
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
  6. Changed1 schema field changed
    • addedInput schema / properties / module
      Added value: +{
      +  "description": "Restrict to organisations with a DECLARED link to one SAP module/product code (UPPERCASE snake_case, e.g. DATASPHERE, BDC, SAC, BW4HANA, S4HANA, JOULE — case-insensitive on input). The declared links are structured data, far more selective than free text: `count_firms_by {by:\"module\"}` gives the live vocabulary with counts. A minority of the directory declares any module at all, so this filter finds the DECLARED specialists — absence from the result means no declared link, never that the firm does not work on the module.",
      +  "type": "string"
      +}
  7. Changed1 schema field changed
    • addedInput schema / properties / cursor
      Added value: +{
      +  "description": "Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place.",
      +  "maxLength": 512,
      +  "type": "string"
      +}
  8. Changed1 schema field changed
    • changedInput schema / properties / kind / description
      Previous value: -"Restrict to one organisation kind. Call list_firm_kinds for live counts. A value outside this list is refused, not silently ignored."New value: +"Restrict to one organisation kind. This list is the vocabulary the corpus holds today, not a frontier — call list_firm_kinds for the live one. A malformed code is refused; a well-formed code the corpus does not hold returns no rows. Neither case is silently ignored, and neither widens the result."
  9. Changed1 schema field changed
    • changedInput schema / properties / kind / enum
      Previous value: -[
      -  "agency_placement",
      -  "professional_services_big4",
      -  "professional_services_esn",
      -  "vendor_sap",
      -  "vendor_partner",
      -  "platform_marketplace",
      -  "community_group",
      -  "other"
      -]New value: +[
      +  "agency_placement",
      +  "professional_services_big4",
      +  "professional_services_esn",
      +  "vendor_sap",
      +  "vendor_partner",
      +  "platform_marketplace",
      +  "community_group",
      +  "client_enterprise",
      +  "other"
      +]
  10. Changed1 schema field changed
    • changedInput schema / properties / kind / enum
      Previous value: -[
      -  "agency_placement",
      -  "professional_services_big4",
      -  "professional_services_esn",
      -  "vendor_sap",
      -  "vendor_partner",
      -  "platform_marketplace",
      -  "community_group"
      -]New value: +[
      +  "agency_placement",
      +  "professional_services_big4",
      +  "professional_services_esn",
      +  "vendor_sap",
      +  "vendor_partner",
      +  "platform_marketplace",
      +  "community_group",
      +  "other"
      +]
  11. Changed6 schema fields changed
    • removedOutput schema / properties / rows / items / properties / citation_note
      Removed value: -{
      -  "type": "string"
      -}
    • changedOutput schema / properties / rows / items / properties / hq_country / type
      Previous value: -"string"New value: +[
      +  "string",
      +  "null"
      +]
    • changedOutput schema / properties / rows / items / properties / kind / type
      Previous value: -"string"New value: +[
      +  "string",
      +  "null"
      +]
    • changedOutput schema / properties / rows / items / properties / name / type
      Previous value: -"string"New value: +[
      +  "string",
      +  "null"
      +]
    • changedOutput schema / properties / rows / items / properties / slug / type
      Previous value: -"string"New value: +[
      +  "string",
      +  "null"
      +]
    • changedOutput schema / properties / rows / items / properties / website / type
      Previous value: -"string"New value: +[
      +  "string",
      +  "null"
      +]
  12. Changed2 schema fields changed
    • addedOutput schema / properties / rows / items / properties / citation_note
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / rows / items / properties / citation_scope
      Added value: +{
      +  "enum": [
      +    "record",
      +    "section_hub"
      +  ],
      +  "type": "string"
      +}
  13. Changed1 schema field changed
    • changedInput schema / properties / kind / description
      Previous value: -"Restrict to one organisation kind. Call list_firm_kinds for live counts."New value: +"Restrict to one organisation kind. Call list_firm_kinds for live counts. A value outside this list is refused, not silently ignored."
  14. First observed

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only/idempotent/closed-world, yet the description adds substantial behavior beyond them: keyed pagination with a 10-row free tier and no cursor, cursor invalidation when filters change, and result ordering rules (name-match tiers when `query` is set, quality ranking otherwise). It also resolves a real ambiguity — that `is_client` outranks `client_enterprise` when flag and label disagree — which prevents systematically wrong filtering.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded, but the middle section is bloated: the flag-vs-kind point is restated three ways ('The two are different columns', 'the flag decides', 'read the flag's meaning into the answer and not the label's') and the pagination warning is shouted in caps. The same information could be delivered in roughly half the words without losing meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 7 optional parameters, an output schema, and full annotation coverage, this description covers everything an agent needs to call it correctly: pagination lifecycle, ordering, filter interaction, and the one semantic trap (flag vs kind) that would otherwise corrupt the answer. Return fields are mentioned but the output schema carries the detail.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description still earns above that by explaining ordering semantics for `query` (word-substring narrowing, name-match-first ordering) and the cursor round-trip contract. The `module` caveat about declared-vs-actual coverage is a genuine interpretive aid, though most of the per-parameter text is duplicated in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (search) and resource (the published Analytics Legends directory of SAP AI & analytics service providers), then enumerates the kinds it covers. It distinguishes itself from data-adjacent siblings by explicitly scoping out SAP end-customer companies, which is exactly the boundary between this tool and find_sap_clients/find_sap_client_profile.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit WHEN-NOT ('SAP END-CUSTOMER companies are NOT in this directory') and points to sibling tools for vocabulary discovery (list_firm_kinds, count_firms_by). It does not name the sibling the excluded data actually lives in (find_sap_clients), so the alternative is implied rather than stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.