Skip to main content
Glama

Analytics Legends — SAP AI & Analytics MCP Server

Server Details

SAP AI & analytics market data for agents: SAP firms, day rates, jobs, Joule & BDC news

If you are the author of this connector, you can claim ownership by verifying the domain or GitHub account it belongs to. Claimed connector authors can inspect health checks, view analytics, and manage their listing.
Status
Healthy
Uptime
100.0% over 63 days
Last Tested
Transport
Streamable HTTP · MCP 2025-11-25
URL
Repository
analyticslegends/analytics-legends-mcp
GitHub Stars
0
Server Listing
ai.analyticslegends/sap-analytics

TDQS

A4.1/5.0

Scored across 22 tools

Disambiguation4/5

The descriptions work unusually hard to separate overlapping tools: find_academy_modules explicitly warns it is not list_sap_modules, search_firms is distinguished from find_sap_clients (services vs end-customers), and get_firm vs get_firm_intel vs search_firms are tiered clearly. Residual confusion remains between the find_/search_/list_ families (find_partner_candidates vs search_firms vs list_firm_kinds) and between get_concept and get_concept_card, though each boundary is spelled out.

Naming Consistency4/5

Every name is snake_case verb_noun, so the pattern is readable and predictable. The verb set is broad and unevenly applied, however — find_, search_ and list_ all retrieve collections (find_academy_modules vs list_sap_modules, search_firms vs list_firm_kinds), and count_firms_by/query_knowledge_graph break the verb_noun shape.

Tool Count4/5

22 tools is on the heavy side, but the server spans genuinely distinct corpora (firms, clients, opportunities, academy, concepts, studies, news, rates, graph) and most pairs like find_academy_modules/get_academy_module or list_studies/get_study are a justified free-metadata/paid-body split. No obvious filler tool; it sits just over the comfortable range rather than sprawling.

Completeness4/5

For a read-only knowledge/corpus server the surface is broad: search, get, list and count operations exist for every major dataset, plus a cross-cutting knowledge graph and an aggregate expansion brief. Gaps are mostly deliberate and documented (no consultant data at any tier, no certification data, rate_band never served), leaving only minor dead ends such as no per-article news fetch beyond source_url.

Available Tools

22 tools
count_firms_byCount the firm directory by country, region, kind, module or SAP signalA
Read-onlyIdempotent
Inspect

Answer a COUNTING question about the published firm directory in one call: how many organisations per country, per REGION (the six-value geography), per kind, per declared SAP module, or per SAP signal band — with the same country/kind/module/query filters search_firms takes, so you can count a slice as easily as the whole. Use this instead of paging search_firms and tallying rows: the directory holds thousands of organisations, and reading them all to produce a table of counts costs hundreds of calls and megabytes of rows for numbers Postgres computes in one scan. Every bucket is a value the directory actually stores; value: null is a real bucket meaning the field is unknown for those rows, and it is served rather than hidden — a country table that silently drops the rows with no country adds up to less than the population and says nothing about it.

ParametersJSON Schema
NameRequiredDescriptionDefault
byYesWhich facet to count on. Required — there is no default worth guessing.
kindNoRestrict to one organisation kind before counting, same vocabulary as search_firms. Combining it with `by:"kind"` is legal and returns that single bucket.
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.
moduleNoRestrict to organisations with a DECLARED link to one SAP module code before counting, same vocabulary as search_firms' `module`. Combining it with `by:"module"` is legal and returns that single bucket (plus the null bucket).
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

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already cover readOnly/idempotent/non-destructive, but the description adds real behavior the agent could not infer: `value: null` is a served bucket meaning the field is unknown, and dropping it would silently under-count the population. This is exactly the kind of non-obvious trait worth disclosing.

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

Conciseness4/5

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

Purpose and the search_firms contrast are front-loaded, and the null-bucket caveat is a worthwhile final sentence. The prose is dense with em-dashes and long clauses, so it is slightly heavier than needed, but no sentence is filler.

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?

For a six-parameter aggregation tool with full schema coverage, an output schema, and safety annotations, the description supplies the remaining decision-relevant context (when to use it, how nulls are handled). Nothing needed to call it correctly is missing.

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, but the description adds legitimate meaning: filters reuse search_firms' vocabulary, `by` has no safe default, and the null bucket semantics affect how filter results should be read. It does not restate the enum values.

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 ('Answer a COUNTING question') and resource ('the published firm directory') and enumerates the five facets counted. It explicitly distinguishes itself from search_firms, so an agent can route without opening either schema.

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

Usage Guidelines5/5

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

Explicitly names the alternative ('Use this instead of paging `search_firms` and tallying rows') and gives the condition that selects this tool (directory holds thousands of orgs; reading all rows costs hundreds of calls). Also notes the filters mirror search_firms' vocabulary.

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

find_academy_modulesSearch the Academy training catalogueA
Read-onlyIdempotent
Inspect

Search the Analytics Legends Academy — the written training modules on SAP Datasphere, Business Data Cloud, SAP Analytics Cloud, BW/4HANA and Databricks — by track, level and free text. _meta.tranche_total_row_count carries the live catalogue size on every call; it is the only count to quote. Returns the catalogue entry: id, slug, EN/FR title, track, level, duration in minutes, tags and the editor's summary. DO NOT CONFUSE IT WITH list_sap_modules, which serves a different population under the same word: that one is the 40-row PRODUCT taxonomy (codes such as SAC, DATASPHERE) used to normalise product wording. This one is the course catalogue. Without query, rows come back in the catalogue's own CURRICULUM order — the order a reader is meant to take them in — track by track. This catalogue is written training, NOT SAP certification tracks: this server publishes no certification data at any tier, so a certification question has no answer here rather than a partial one. CATALOGUE ONLY — the module BODY is subscriber content, served by get_academy_module on this same endpoint with a subscriber key (Consultant tier or above), which is the same door the €29.90 Consultant Pass opens on the site. On THIS endpoint the machine-access subscription is the MCP Pass (€39.90/month, analyticslegends.ai/pricing/), which opens the ENTIRE paid tranche from one key; the €29.90 Consultant Pass is its web-subscriber equivalent and opens the same tier floor here. status and is_preview are SERVED, never filtered on: they are the two flags the platform marks free access with, they do not coincide (measured 2026-08-16: 38 rows status='available', 56 rows is_preview), and you decide which one your answer needs. PAGINATED WITH A KEY (a free one is enough): pass _meta.next_cursor back as cursor with the same filters until it is null; without a key, first 10 rows only. Read _meta.available_tracks and _meta.available_levels — both counted on the served population at call time — before assuming a facet value exists.

ParametersJSON Schema
NameRequiredDescriptionDefault
levelNoRestrict to one level, matched case-insensitively: Beginner · Intermediate · Advanced · Expert. Counts in `_meta.available_levels`.
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.
trackNoRestrict to one track, by SLUG (`databricks-data-eng`) or by English name (`Databricks & Data Eng.`), matched case-insensitively. The live vocabulary with per-track counts is `_meta.available_tracks` on every response. The numeric track_id is deliberately NOT accepted — it is an internal counter, and passing `6` would look like naming a subject.
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.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.7/5.0
Behavior5/5

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

Annotations already declare the safe read-only, idempotent, closed-world profile, and the description adds substantially more: pagination is key-gated (free key gives cursor paging, no key = first 10 rows only), `status`/`is_preview` are served but never filtered on and do not coincide, `_meta.tranche_total_row_count` is the only count to quote, and body access requires a Consultant-tier subscriber key. This is real operational context beyond the annotations.

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?

Front-loaded with the purpose, but the single block runs long and carries pricing detail (€29.90 Consultant Pass vs €39.90 MCP Pass) that is tangential to invoking the tool, plus repeated emphasis on the catalogue-vs-certification point. Information-dense rather than wasteful, but it does not earn every sentence.

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?

For a 5-parameter, zero-required search tool with an output schema and rich annotations, nothing an agent needs is missing: filtering, ordering semantics, pagination protocol, auth tier, and the meaning of the served flags are all covered. The return-field list is even summarised despite an output schema existing.

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 schema already carries the parameter mechanics; the description nonetheless adds value by pointing at the live `_meta.available_tracks`/`available_levels` vocabularies as the source of valid facet values and by explaining that `track_id` is deliberately rejected. It stops short of adding syntax the schema lacks, so it sits just above the 3 baseline.

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 Academy written-training catalogue) and immediately distinguishes itself from the two siblings that share overlapping vocabulary — `list_sap_modules` (the 40-row product taxonomy) and `get_academy_module` (the module body). An agent can route correctly without opening any schema.

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

Usage Guidelines5/5

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

Explicit when-to-use (search the course catalogue by track/level/free text), explicit when-not (certification questions have no answer here, not a partial one; module bodies belong to `get_academy_module`), and names the sibling to use instead for product normalisation. It even tells the agent to read `_meta.available_tracks`/`available_levels` before assuming a facet value exists.

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

find_opportunitiesSearch the public SAP AI & analytics contract radarA
Read-onlyIdempotent
Inspect

Search every SAP contract and permanent-role posting Analytics Legends publishes to an ANONYMOUS visitor. Only the SITE RADAR leg below is the population a human browses on /opportunities/, where each of its postings has its own prerendered page; the PROMOTED leg is served to agents only — it is not listed on /opportunities/ and its rows have no page of their own (citation_scope "section_hub"). It merges the platform's TWO public legs, which are near-disjoint (measured 2026-07-30: 1 row in common): (a) the PROMOTED feed (public.public_opportunities) — general SAP work (FI/CO, SD, EWM, MDG, BTP, ABAP), all German cities, dated (posted_at is populated on all but a few % of the live rows of that leg — the undated rows are sorted LAST and paginate like the others). 🔴 THIS LEG CHANGED SHAPE ON 2026-08-28: until then it was fed by three keyless APIs and carried no contract_type, country_code, expires_at or rate at all; it was then loaded from the site radar and now declares contract_type and country_code on most of its rows, an expiry on most, and an advertised rate on a small minority. Do NOT assume a field is null on this leg — read the _meta counters on YOUR OWN response, which are computed at query time; (b) the SITE RADAR (/api/contracts-lean.json) — these carry country, category, seniority, posted_at, employment_type and, on most of them, expires_at; they are the analytics-specific ones (SAC Planning, Datasphere Technical Lead, Business Data Cloud). READ employment_type BEFORE CALLING THIS A CONTRACT MARKET: the radar is mostly PERMANENT roles, so an unfiltered page answers a freelance question with salaried jobs unless you filter. The argument of the same name does the filtering, and _meta.tranche_total_row_count on your own response is the live population — read the split from a filtered call, never from a figure quoted in this text. TWO DIFFERENT RATE FIELDS, AND THEY MEAN DIFFERENT THINGS. currency / daily_rate_min / daily_rate_max are the posting's OWN advertised rate and are almost always null — most listings publish no rate at all. rate_band is the platform's editorial benchmark for that posting's (seniority × product × region) cell. It is a SUBSCRIBER surface of the WEBSITE, not of this server: the radar file this server reads carries no band since 2026-08-29, so rate_band is null on EVERY row here, with or without a key (each response's _meta.rate_band_coverage counts it). The panel grid itself covers European cells only — DACH, FR/Benelux, UK/IE, Southern EU, Eastern EU, Nordics — so a posting in the Americas, APAC, Africa or the Middle East has no band at ANY tier: never promise one for it. It is rate_basis: "panel_inferred" — Eursap n=312 plus the Analytics Legends operator panel, permanent rows restated as a TJM equivalent at ~220 billable days a year — NOT a rate this employer offered. Quote it as a band with its basis, kind and source, never as the posting's rate, and never average bands across postings: many rows share one cell. WHAT IS GATED IS A FIELD, NOT A ROW: on most radar rows source_url is null and application_link reads "members_only" — the verified link to the original listing is the paid Consultant-tier deliverable. Everything else about the posting is public, and citation_url is that posting's own page on analyticslegends.ai. Quote it. Report _meta.tranche_row_count as the published public population, never as the size of the market.

ParametersJSON Schema
NameRequiredDescriptionDefault
langNoReading language for the TITLE — 'EN' (default), 'FR' or 'DE'. This is a RENDERING choice, never a filter: it changes which string `title` carries, never which rows come back. Read `title_lang` on every row for the language actually served: it differs from what you asked for exactly when that translation does not exist (the live FR/DE coverage is counted on every response in `_meta.untranslated_leg`), and the verbatim is served instead, labelled with the language the harvest chain measured. `source_lang` always carries the language the ADVERTISER wrote in, translated or not. The promoted leg has no translated columns at all: its rows ignore this argument and say so with `title_lang: null` — see `_meta.untranslated_leg`.
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.
countryNoISO-3166-1 alpha-2 code, applied to both legs as a predicate on the row's own country_code. It NO LONGER selects the site-radar leg alone: the promoted feed carried country_code on almost none of its rows until 2026-08-28 and now carries it on most, so a country filter now returns both legs. A row still without one is dropped because it does not match, not because its leg was excluded by assumption. `_meta.match_count_by_leg` shows what each leg contributed on YOUR call — read the split there, never from a figure quoted in this text.
locationNoCity or place, matched case-insensitively as a substring of the posting's location. The promoted leg is all-German (Hamburg, Frankfurt am Main, Bremen, Munich, Cologne, Dortmund, Hanover, Landshut, Mannheim, Stuttgart); the site-radar leg is worldwide.
remote_modeNoRestrict to one work-location policy: `remote`, `hybrid` or `onsite`. READ THIS BEFORE ANSWERING A REMOTE QUESTION: a large share of the radar declares no policy at all (`_meta.remote_mode_undeclared` carries the live count — roughly half the radar when last measured, and a frozen pair written here drifted ~30% in two days), and an undeclared row is NOT an on-site row — it is a posting that does not say. Any value here therefore sets those rows aside rather than classifying them, exactly as the site's own filter does, and `_meta.remote_mode_undeclared` reports how many were set aside. The promoted leg carries its own `remote_mode` column and is filtered by the same predicate. Read `_meta.available_remote_modes` for the live spread before assuming a value exists.
employment_typeNoRestrict to one engagement type. THE RADAR IS MOSTLY PERMANENT, so a freelance or contract question answered off an unfiltered page is answered with salaried jobs. For the actual split, make the filtered call and read `_meta.tranche_total_row_count` — it is counted at query time. The promoted leg declared NO contract_type until 2026-08-28 and now declares one on most of its rows, so a value here no longer drops that leg wholesale — only the rows still silent. THOSE ROWS ARE NOT A FOURTH TYPE AND NOT PERMANENT ONES: `_meta.available_employment_types` counts only what declares, and `_meta.employment_type_undeclared` carries the rest, so the two together are the rows THIS call read (`_meta.employment_mix_rows_read` — a window, NOT the tranche: `_meta.tranche_total_row_count` is the population) and either alone is not even that. Read both before quoting a mix, and quote the window with it.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4/5.0
Behavior5/5

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

Annotations already cover readOnly/idempotent/non-destructive, and the description still adds substantial non-structured behavior: source_url/application_link are gated at Consultant tier while the row itself is public, citation_url is the quotable canonical page, rate_band is editorially inferred and never the employer's rate, and rate_band is null on every row as of 2026-08-29. It also flags the 2026-08-28 shape change on the promoted leg so an agent does not over-infer nulls.

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

Conciseness2/5

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

The first sentence is well front-loaded, but the body is a wall of prose with emoji markers, redundant restatements ('read the `_meta` counters on YOUR OWN response' recurs), and long parenthetical asides about measurement dates. Much of the content is legitimate complexity, but the same warning is repeated three or four times and could be cut by half without losing meaning.

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

Completeness4/5

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

For an 8-parameter tool with two heterogeneous legs, gated fields, and misleadingly-named rate fields, the description covers nearly everything an agent needs: leg semantics, gating, null behavior, filter side effects, and which _meta counters to cite. Given an output schema exists, some of the response-field detail is redundant rather than additive, which keeps it from a 5.

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 baseline is 3; the description earns the extra point by adding semantics the schema alone would not convey — that lang is a rendering choice rather than a filter, that country now spans both legs, that cursor must be replayed with identical filters, and that remote_mode silently sets aside undeclared rows. The rate-field explanation is valuable but describes response fields rather than parameters.

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

Purpose4/5

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

The opening sentence gives a specific verb and resource ('Search every SAP contract and permanent-role posting Analytics Legends publishes') and immediately separates the two legs, which is genuinely useful disambiguation. It does not, however, position itself against any of the 20+ siblings (search_firms, get_day_rate_benchmark, find_partner_candidates), so an agent gets no routing help beyond the resource name itself.

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?

There is strong conditional guidance: read employment_type before treating this as a contract market, do not assume a null field on the promoted leg, read _meta counters on your own response, rate_band is null everywhere, country now applies to both legs. What is missing is when to pick this over a sibling tool (e.g. versus get_day_rate_benchmark for rate questions), so usage guidance is thorough within the tool but not comparative.

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

find_partner_candidatesLocal partner or acquisition candidates for a market entry (Legend tier)A
Read-onlyIdempotent
Inspect

Rank the LOCAL service providers of a target country or region — the firms a practice partners with, subcontracts to, or acquires to enter a market. local_only (default true) keeps firms with NO sister entity in another country (read from the brand-family key of the SSOT): a subsidiary of a global group is a competitor, not a partner. Filter by kind and by DECLARED SAP module; rows carry the declared modules, the sister countries of the brand (when any), the count of declared partnerships and the public directory fields. Ordered by SAP signal, then data completeness. country or region is required: a partner search without a target market is the whole directory. Paginated by cursor. Requires a subscriber API key, Legend tier or above. No person, no e-mail — outreach details stay behind the platform's signed-URL path.

ParametersJSON Schema
NameRequiredDescriptionDefault
kindNoRestrict to one provider kind (placement agencies, ESN/SI practices, Big-4, SAP partners, platforms). SAP's own entities, communities, schools and public bodies are never in this population.
limitNoMax rows (hard cap 50).
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 firms with a DECLARED link to one SAP module code (UPPERCASE snake_case, e.g. DATASPHERE, BDC, SAC — call count_firms_by {by:"module"} for the live vocabulary). Absence means no declared link, never incapacity.
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.
local_onlyNotrue (default): only firms with no sister entity abroad. false: every provider of the market, groups included.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.6/5.0
Behavior5/5

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

Annotations already declare the safe read-only/idempotent profile, but the description adds real operational context: subscriber API key with Legend-tier prerequisite, cursor pagination semantics, the ordering rule (SAP signal then data completeness), the row contents, and the privacy constraint that no person or e-mail is returned. This is information an agent cannot recover from the annotations.

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

Conciseness4/5

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

Purpose and the key scoping constraint (local_only) are front-loaded, and every clause carries information. It is a dense single block and repeats some schema content (e.g. the local_only default), which keeps it from a 5 but is justified by the tool's complexity.

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?

Despite an output schema existing, the description still covers row shape, ordering, auth prerequisites, and pagination, and it flags the mandatory target market and the competitor-vs-partner distinction. An agent has everything needed to invoke it correctly without opening the schema.

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, but the description goes beyond the field definitions by explaining the intent behind local_only (read from the brand-family key of the SSOT) and clarifying that a missing module link means no declared link rather than incapacity. It adds rationale and ranking semantics rather than merely restating 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?

Specific verb ('Rank') plus a precisely scoped resource ('LOCAL service providers of a target country or region' for partnering, subcontracting, or acquisition). It is clearly distinguishable from sibling searches like search_firms or find_sap_clients because it names the market-entry/partner-selection population and even excludes competitors via local_only.

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 strong contextual guidance: the local_only rationale (a global subsidiary is a competitor, not a partner), the requirement of country or region ('a partner search without a target market is the whole directory'), and points to count_firms_by for the live module vocabulary. It never explicitly states when to prefer a sibling over this tool, so it stops short of a 5.

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

find_sap_clientsSearch the SAP end-customer corpus (Legend tier)A
Read-onlyIdempotent
Inspect

Search the SAP END-CUSTOMER corpus — the companies that RUN SAP, not the firms that sell services (those are search_firms). This is the paid Legend+ dataset locked away from the public surface on 2026-07-08; it requires a subscriber API key, Legend tier or above. Verification status is SERVED, never silently filtered: sap_client_verification_status and status are columns on every row ('verified' on a few hundred of ~15k rows — the exact counts are in _meta on every call, never in this text), and you decide what standard of proof your answer needs. product filters on the detected-adoption flags every profile already carries (the uses_* columns get_sap_client_profile serves): it keeps only rows where that product was DETECTED. A row it drops is 'not detected by our detection pass', never 'does not use it' — detection is a positive signal with no negative counterpart.

ParametersJSON Schema
NameRequiredDescriptionDefault
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.
countryNoISO-3166-1 alpha-2 country code, e.g. DE, FR, CH.
productNoKeep only end-customers where this SAP product was DETECTED in use. Absence from the result means undetected, not unused.
industryNoIndustry or sector filter, matched case-insensitively.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.6/5.0
Behavior4/5

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

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), and the description adds real behavioral context: paywall/tier gating, the fact that verification status is always served and never silently filtered, and that detection is a positive-only signal. It stops short of describing result shape or paging behavior in detail, but that is covered elsewhere.

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

Conciseness4/5

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

Front-loaded and dense with useful distinctions, and every sentence carries information. It is on the long side, with a slightly meta aside about where counts live ('never in this text'), but nothing is filler.

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?

A filtered-list tool with an output schema and full annotation coverage; the description supplies the tier gating, detection semantics, and cursor behavior an agent needs, and correctly omits return-value detail that the output schema and `_meta` handle.

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, but the description adds genuine semantic depth the schema lacks: the meaning of a dropped row under `product` (undetected, not unused) and the location of verification status columns. This goes beyond restating field definitions.

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+resource (search the SAP END-CUSTOMER corpus) and immediately disambiguates from the nearest sibling by naming search_firms as the service-provider corpus. An agent can route correctly without opening any schema.

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

Usage Guidelines5/5

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

Explicitly states who this is for (companies that RUN SAP, not sellers), the access precondition (subscriber API key, Legend tier or above), and the alternative (search_firms). It also names get_sap_client_profile as the source of the uses_* columns, giving a clear sibling handoff.

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

get_academy_moduleRead an Academy module (Consultant tier)A
Read-onlyIdempotent
Inspect

Read one Academy training module in full — body, learning objectives and summary, EN, FR and DE — the written course corpus the €29.90 Consultant Pass sells. On THIS endpoint the machine-access subscription is the MCP Pass (€39.90/month, analyticslegends.ai/pricing/), which opens the ENTIRE paid tranche from one key; the €29.90 Consultant Pass is its web-subscriber equivalent and opens the same tier floor here. Requires a subscriber API key (Authorization: Bearer alk_…), Consultant tier or above; without one this tool refuses and find_academy_modules keeps serving the catalogue. Takes the module id (M001) or its slug (datasphere-foundations), both matched case-insensitively — find_academy_modules returns both on every row, and query_knowledge_graph returns the same ids as module:M001 node ids, so a graph walk now ENDS somewhere. Unlike get_study, the whole module is served in one call: the longest body measured is 17 865 characters, two orders of magnitude under the response ceiling, so sectioning it would cost the caller context without protecting anything.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesModule id (`M001`) or slug (`datasphere-foundations`), verbatim from find_academy_modules.rows[].id / .slug.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, so the bar is lower. Even so, the description adds substantial behavioral context: authentication requirements, refusal behavior without a key, case-insensitive matching, multilingual content, and a measured response-size justification for not sectioning the module. No contradiction with annotations.

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

Conciseness4/5

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

The description is quite long but each section carries relevant information: purpose, auth, input matching, sibling comparison, and response sizing. It is front-loaded with the core purpose. However, the pricing/tier explanation and graph-walk narrative add more marketing context than strictly necessary for invoking the tool.

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?

Given the single parameter, existing output schema, and rich annotations, the description is effectively complete. It covers what the tool returns, authentication prerequisites, failure behavior, input variants, and how this tool relates to siblings. An agent has everything needed to select and call it correctly.

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?

The schema already fully documents the single id parameter with format examples and provenance, so the baseline is 3. The description adds meaningful extra semantics: both id and slug are accepted case-insensitively, and the id matches the values returned by find_academy_modules and query_knowledge_graph. This goes a bit beyond the schema, justifying a 4.

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?

The description opens with a specific verb and resource: 'Read one Academy training module in full — body, learning objectives and summary, EN, FR and DE'. It also explicitly distinguishes itself from sibling tools by explaining that find_academy_modules only serves the catalogue and that get_study does not return the whole module in one call.

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

Usage Guidelines5/5

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

The description gives clear guidance on when to use this tool versus alternatives: without a subscriber key it refuses and find_academy_modules keeps serving the catalogue; unlike get_study, this endpoint returns the entire module in one call. It also tells the agent where to source the id/slug (from find_academy_modules rows) and how query_knowledge_graph ids relate.

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

get_conceptConcept metadata and editor's summary (public)A
Read-onlyIdempotent
Inspect

Fetch one concept entry by slug: title, category, level, tags and the editor's summary. Written by a named human editor, not generated. level is GRADED on every active row since 2026-09-18 (the CHECK constraint accepted only the legacy vocabulary OR NULL, so the loader wrote NULL rather than fail; 108 of 330 were blank). A null, if one ever returns, means 'not graded', never 'Beginner'. The card body, cheat sheet, glossary, pro tip and the four analysis tables are subscriber content and are NOT returned. Why-it-matters and key points are not returned here either, but they ARE published in full on the concept page at citation_url — follow the URL for those.

ParametersJSON Schema
NameRequiredDescriptionDefault
slugYesConcept slug from search_concepts.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.6/5.0
Behavior5/5

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

The description adds significant behavioral context beyond annotations: it explains the level grading history (NULL meaning 'not graded', never 'Beginner'), the fact that content is human-edited, and the exact handling of null values. This goes well beyond the readOnlyHint and idempotentHint annotations, giving the agent critical interpretation guidance. No contradiction with annotations.

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

Conciseness4/5

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

The description is structured with the primary purpose first, followed by important data-quality caveats and exclusion lists. Each sentence adds necessary information, and the length is justified given the complexity of the null handling and the need to clarify what is not returned. It is slightly longer than typical but well-organized.

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?

Given the output schema exists (per signal), the description sufficiently covers what an agent needs: the parameter, the returned fields, the null semantics, and the alternative for missing content. The caveat about subscriber content and the citation_url are crucial for correct usage. No gaps that would prevent a correct call.

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

Parameters3/5

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

The schema covers 100% of the single parameter (slug) with a description referencing search_concepts. The tool description adds no new meaning beyond what the schema already provides, so a baseline of 3 is appropriate. The description merely restates 'by slug' without elaborating on format or constraints.

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?

The description clearly states the tool fetches a single concept entry by slug and lists the returned fields (title, category, level, tags, editor's summary). It distinguishes from siblings like get_concept_card and search_concepts by explicitly noting what it does not return (card body, cheat sheet, etc.) and where to find that content, making the tool's purpose unambiguous.

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

Usage Guidelines5/5

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

The description provides explicit guidance on when to use this tool: it returns only public metadata and the editor's summary. It explicitly lists what is NOT returned (subscriber content, why-it-matters, key points) and instructs the agent to follow the citation_url for the full page, effectively directing to alternatives. This is clear exclusions and routing.

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

get_concept_cardFull concept card (Consultant tier)A
Read-onlyIdempotent
Inspect

The FULL encyclopaedia card for one concept — body, cheat sheet, glossary, pro tip, and the four analysis tables (decision table, peer comparison, named pitfalls, performance facts), EN, FR and DE, plus why-it-matters and key points (those two are also published free on the concept page; here they come structured, in three languages, in the same payload) — the corpus the €29.90 Consultant Pass sells. On THIS endpoint the machine-access subscription is the MCP Pass (€39.90/month, analyticslegends.ai/pricing/), which opens the ENTIRE paid tranche from one key; the €29.90 Consultant Pass is its web-subscriber equivalent and opens the same tier floor here. Requires a subscriber API key (Authorization: Bearer alk_…), Consultant tier or above; without one this tool refuses and get_concept keeps serving the public metadata. Find slugs with search_concepts.

ParametersJSON Schema
NameRequiredDescriptionDefault
slugYesConcept slug, verbatim from search_concepts.rows[].slug.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.4/5.0
Behavior5/5

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

Annotations already mark readOnly/idempotent/non-destructive, and the description adds key behavioral traits: the paywall boundary, the requirement for a Bearer alk_… key at Consultant tier or above, refusal without it, and the fact that the same payload is returned structured in three languages. This goes well beyond annotation-only signal.

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

Conciseness2/5

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

The first sentence is information-dense, but the description then spends several clauses on pricing and pass-equivalence ('€29.90 Consultant Pass', '€39.90/month', 'analyticslegends.ai/pricing/', 'opens the same tier floor here') that do not help an agent invoke the tool. This could be shortened to the auth requirement and the get_concept fallback.

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?

For a single-parameter read-only tool with full schema coverage and an output schema, the description covers what the card contains, how to obtain a slug, the required authorization, and the fallback behavior. Nothing essential for selecting or calling the tool is missing.

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

Parameters3/5

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

The schema already documents the single slug parameter with 100% coverage, including 'verbatim from search_concepts.rows[].slug'. The description repeats the search_concepts source but adds no new parameter-level meaning, so the baseline score of 3 is appropriate.

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?

The description opens with 'FULL encyclopaedia card for one concept' and enumerates the payload: body, cheat sheet, glossary, pro tip, four analysis tables, and three languages. It also contrasts itself with get_concept, which continues serving public metadata, making the tool's role unambiguous.

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

Usage Guidelines5/5

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

It explicitly states the subscription/auth prerequisite ('Requires a subscriber API key... Consultant tier or above'), the failure mode without it ('this tool refuses'), and the alternative ('get_concept keeps serving public metadata'). It also directs callers to find slugs with search_concepts, which is the correct retrieval path.

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

get_day_rate_benchmarkPublic SAP AI & analytics day-rate aggregateA
Read-onlyIdempotent
Inspect

The PUBLIC day-rate aggregate for SAP AI & analytics freelance work: min/max daily rate by country, specialisation and seniority — daily_rate_min / daily_rate_max (plus daily_rate_median, daily_rate_p10, daily_rate_p90 when the source publishes them), every amount in the row's own currency — with source, source date, confidence and the sample the source states. This is the free aggregate published at analyticslegends.ai/api/market-rates.json, and it is SMALL — a few dozen rows at most, every one of them a secondary source (a published market study or a job-board scan), and sample_size is null on most of them. NO COUNT IS WRITTEN HERE ON PURPOSE: _meta.tranche_total_row_count and the rows themselves are the live measure. A frozen pair stood here until 2026-08-27 — '11 rows on 2026-08-09 … sample_size null on 8 of them' — and the second half was WRONG (7 of 11) while the first was still right, which is the whole argument against writing either. NOTHING IS HELD BACK BEHIND IT: there is no paid counterpart to this aggregate. The community-contribution path exists (public.rate_contributions) but publishes nothing yet — v_community_rate_aggregates and v_rate_index are still empty, because a contributed rate only surfaces once a cell holds enough submissions to be reported without identifying anyone. So whatever percentile a source row happens to carry is served here, free, to everyone. The GB row carries a median, p10 and p90, and its own note says its min/max ARE the 25th and 75th percentiles. What is missing from this answer is missing from THIS aggregate; it is not a paid tier. THIS IS NOT THE ONLY RATE THE PLATFORM PUBLISHES, AND ON THE QUESTIONS THIS MARKET ASKS MOST IT IS THE THINNER ONE. The website carries a rate_band per live radar posting for signed-in subscribers — a panel-inferred P25–P75 band per (seniority × product × region) cell, Eursap n=312 plus the Analytics Legends operator panel. This server does not serve it (find_opportunities.rate_band is null on every row, with or without a key). The grid covers EUROPEAN cells only — DACH, FR/Benelux, UK/IE, Southern EU, Eastern EU, Nordics — and there it prices exactly the cells this small aggregate cannot (Senior Datasphere DACH, Senior BDC DACH), where specialisation:"bdc" here returns nothing. When this tool comes back empty for a country × product: in one of those European regions, say the AGGREGATE holds no row and that subscribers read the platform's band on the posting pages; ANYWHERE ELSE (Americas, APAC, Africa, Middle East) say the platform publishes no rate for that cell at any tier — never that subscribers get one. The two are different instruments: this one is a published market study, that one is an editorial benchmark attached to a live posting. Read _meta.available_countries / available_specialisations / available_seniorities — they are computed from the aggregate on every call — before concluding that a rate is unpublished, and quote each row with its own currency, its confidence and its source date.

ParametersJSON Schema
NameRequiredDescriptionDefault
countryNoISO-3166-1 alpha-2 country code, e.g. DE, FR, CH.
seniorityNoe.g. senior, principal.
specialisationNoA specialisation code, matched exactly. The live list is `_meta.available_specialisations` on every call — no list is kept here, because a written one went stale. Codes are NOT evenly spread across countries: read the rows a country filter returns. A code this aggregate does not hold (bdc, joule) returns 0 rows and the available codes; it never widens to a neighbouring band. 0 rows here is an absence in THIS aggregate only — see the tool description for what the website's subscriber band covers and where no rate exists at any tier.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A3.6/5.0
Behavior4/5

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

Annotations already declare readOnly, idempotent, openWorld, non-destructive, so the safety profile is covered. The description goes well beyond them: the result set is small (a few dozen rows), every row is a secondary source, sample_size is null on most rows, amounts are in each row's own currency, and bdc/joule return 0 rows rather than widening. This is meaningful disclosure about data shape and limits that the annotations cannot convey.

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

Conciseness2/5

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

Purpose is front-loaded in the first clause, but the body is bloated with self-justifying digressions — the 'frozen pair stood here until 2026-08-27 … the second half was WRONG (7 of 11)' anecdote and the argument about why no count is written. These do not help an agent select or invoke the tool and crowd out the genuinely useful routing rules.

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

Completeness4/5

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

For a 3-optional-parameter, read-only aggregate with an output schema and full annotation coverage, the description supplies the edge-case semantics an agent most needs: empty-result interpretation differs by region, and _meta.available_* must be read before declaring a rate unpublished. It is arguably over-complete, but nothing an agent needs is missing.

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

Parameters3/5

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

Schema coverage is 100% and the schema descriptions are themselves rich (the specialisation field already documents exact matching, the live _meta list, and 0-row behaviour). The description largely repeats that advice rather than adding new syntax or format detail, so the baseline 3 applies; the per-row currency guidance is about output, not parameters.

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

Purpose4/5

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

The opening clause states a specific resource and content: a PUBLIC day-rate aggregate giving min/max daily rate by country, specialisation and seniority. It also explicitly distinguishes itself from the sibling instrument (find_opportunities.rate_band, which it does not serve). The signal is clear but has to be excavated from a great deal of surrounding argument.

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?

It gives unusually explicit routing rules: when a country × product query returns empty inside a European grid region, say the aggregate holds no row; anywhere else, say no rate is published at any tier. It names the alternative instrument and its European-only coverage. What it lacks is ordinary when-to-call-this guidance relative to siblings like find_opportunities, since the guidance is framed around how to narrate an empty result rather than when to select the tool.

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

get_expansion_briefExpansion brief for a target region (Legend tier)A
Read-onlyIdempotent
Inspect

The one-call brief a services practice reads before opening a subsidiary in another region: providers already there (by kind and by country, and how many are LOCAL entities versus members of multi-region groups), SAP end-customers there (by country and by industry — counts from the Legend corpus, never rows), live opportunities by country, and the BRIDGE GROUPS — brand families that hold entities both in the target region and in your home region (or anywhere else when home_region is omitted). Regions are the platform's six-value geography: EMEA holds Europe AND the Middle East, AFRICA is its own region, AMER is North America, LATAM and APAC are what they say, GLOBAL is the country-unknown bucket and is refused as a target. Day rates are NOT in this brief: call get_day_rate_benchmark per country; it answers only for the countries listed in its available_countries (Europe, the Middle East, North America and a few APAC countries at low/medium confidence) and refuses the rest rather than transpose a European band. Every count is read at query time from the SSOT. Requires a subscriber API key, Legend tier or above.

ParametersJSON Schema
NameRequiredDescriptionDefault
home_regionNoOptional. The region you operate from today. When set, bridge groups are the families present in BOTH regions; when omitted, in the target region and any other.
target_regionYesThe region you want to enter. One of EMEA, AMER, LATAM, APAC, AFRICA (GLOBAL is refused: it is not a market).

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already establish readOnly/idempotent/non-destructive, so the bar is lower, and the description still adds real context: counts are read at query time from the SSOT, an API key at Legend tier or above is required, and GLOBAL is refused. It does not cover rate limits or cost, but the auth and freshness disclosures are substantive.

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?

It is one dense paragraph packing several distinct facts (brief contents, region taxonomy, day-rate handoff, SSOT freshness, auth tier) with no structural breaks, which makes it slow to scan. Most sentences do carry information, but the whole could be split and front-loaded more aggressively.

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?

An output schema exists, so return values need not be described. With that removed from scope, the description covers inputs, region semantics, refusals, data source, auth tier, and the alternative for day rates — everything an agent needs to call it correctly.

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 description coverage is 100% and the schema already defines both enums and the home_region bridge semantics, so the baseline is 3. The description adds value beyond the schema by clarifying the six-value geography (EMEA includes the Middle East, AFRICA is standalone, GLOBAL is the country-unknown bucket) and why GLOBAL is invalid as a target.

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 deliverable (a pre-subsidiary-opening brief) and enumerates its sections: providers by kind/country with LOCAL vs multi-region split, SAP end-customers by country/industry, live opportunities, and bridge groups. It explicitly distinguishes itself from the sibling that handles a related need (get_day_rate_benchmark) and from row-level tools by noting counts 'never rows'.

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

Usage Guidelines5/5

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

Gives clear when-to-use context (reading a brief before opening a subsidiary in another region) and routes the adjacent need elsewhere: 'Day rates are NOT in this brief: call get_day_rate_benchmark per country.' It also states an explicit exclusion (GLOBAL is refused as a target region) and explains the home_region omitted-vs-set behavior.

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

get_firmGet one firm's published profileA
Read-onlyIdempotent
Inspect

Fetch one organisation from the published directory by its database slug (rows[].slug from search_firms, verbatim). Returns the same public fields plus partnerships_declared, the count of partnerships this directory records for the firm — 0 on ~77 % of rows (re-measured 2026-09-30 on the published tranche: 77,1 %; it read ~97 % until the 2026-09-30 brand-family pass derived 3 730 sibling links from sister entities), meaning none recorded here, never that the firm has no partners; a sibling link is a reading of shared brand, not a declared org chart. Does not return the paid firm-intelligence profile, contacts, or any person.

ParametersJSON Schema
NameRequiredDescriptionDefault
slugYesThe DATABASE slug, taken verbatim from search_firms.rows[].slug. It is not always the web slug in citation_url: a minority of published rows carry a numeric firm id instead (365 of them on 2026-08-09 — the population is read at query time and returned as `_meta.tranche_total_row_count`, never written down here). get_firm{slug:"00393"} is GULP, whose page is /companies/gulp/. Deriving a slug from the citation URL fails on those rows, and deriving it from the NAME is not safe either; carry rows[].slug across instead. A slug this tool refuses is not proof the firm is absent from the market or even from the database — the published tranche is an editorial subset, and a row the editor has not published is refused here exactly as a wrong slug is.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.3/5.0
Behavior5/5

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

Annotations already cover the safety profile (readOnlyHint, idempotentHint, non-destructive, closed-world), and the description goes well beyond them by explaining what partnerships_declared actually means, warning that 0 indicates 'none recorded here' rather than 'no partners', and clarifying that a sibling link is an inference of shared brand, not a declared org chart. That interpretation guidance is exactly the kind of context annotations cannot supply.

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?

Front-loaded correctly — action first, then return-shape caveats — but it is over-stuffed with measurement trivia: precise percentages with comma decimals, multiple re-measurement dates, and the historical '~97 % until the brand-family pass' narrative. The core warning about misreading 0 earns its place; the surrounding statistics are heavier than an agent needs.

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?

Single required parameter, output schema present, annotations covering read-only/idempotent/closed-world, and the description still adds the interpretation caveats an agent needs to avoid false conclusions (empty partnerships, slug refusal not implying absence). Nothing required to call it correctly is missing.

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

Parameters3/5

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

Schema description coverage is 100% and the slug property description is itself extremely detailed, so the schema does the heavy lifting. The description reinforces the 'verbatim from search_firms.rows[].slug' rule but adds little semantic detail beyond what the schema already states; baseline 3 applies.

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 and resource ('Fetch one organisation from the published directory') scoped by an exact identifier source, and distinguishes itself from siblings by explicitly excluding the paid firm-intelligence profile (get_firm_intel) and search_firms' list output. An agent can route correctly without opening any schema.

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 concrete context for use: pull rows[].slug from search_firms verbatim to call this. The exclusion of the paid firm-intelligence profile implicitly points to get_firm_intel, but the description never names that alternative or states the condition under which to prefer it, so the when-not side is implied rather than explicit.

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

get_firm_intelFirm intelligence profile (Legend tier)A
Read-onlyIdempotent
Inspect

The paid intelligence profile of a services firm — SAP practice size and partner level, delivery flags per product, typical day rate and seniority, notable clients, analytics practice summary, and a LinkedIn company URL (present on ~71% of the corpus, re-measured 2026-09-05 on 9,104 profiles — it read ~39% from 2026-08-10 to 2026-09-05, i.e. a third of the corpus below the truth, because an enrichment pass filled the column and no reader of this sentence was told — glassdoor_rating, glassdoor_reviews_count and linkedin_followers are null on all but a handful of profiles, absence here is a data gap, not a signal). READ THE SPARSITY BEFORE QUOTING A ROW: on the 9,103 profiles measured 2026-08-23, typical_day_rate_eur is null on 78.0% and sap_partner_level on 85.7% — the two headline fields are the exception, not the rule, and a null means 'not researched', never 'no partner level'. Requires a subscriber API key, Legend tier or above. Person-shaped fields (contacts, founders, leadership, recruiters, postal addresses) are NEVER served by this endpoint at any tier — they remain behind the platform's signed-URL path. Search by name; the public directory (search_firms) is a different, wider population.

ParametersJSON Schema
NameRequiredDescriptionDefault
modeNoKeep only firms whose profile declares this engagement mode (mode_freelance / mode_permanent / mode_subcontract). Same reading as `delivers`: declared-only.
nameNoFirm name, matched case-insensitively; an exact name ranks first, then a name starting with it, then the other matches. Omit to browse the corpus by data completeness.
limitNoMax rows (hard cap 10 — these rows are wide).
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.
countryNoISO-3166-1 alpha-2 country code, e.g. DE, FR, CH.
deliversNoKeep only firms whose profile DECLARES delivery of this product (the `delivers_*` flags every row already carries). An undeclared flag drops the row: absence from the result means the profile does not declare it, not that the firm cannot deliver it.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.3/5.0
Behavior5/5

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

Annotations already declare read-only/idempotent/non-destructive, yet the description layers on substantial extra behavior: the auth/tier requirement, the null-on-all-but-a-few fields, the critical sparsity warning (78% null day rate, 85.7% null partner level), the 'null = not researched, never no partner level' interpretation rule, and the signed-URL exclusion for person fields.

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?

The purpose is front-loaded and much of the content (sparsity warning, tier requirement, field exclusions) earns its place. However the long parenthetical reciting measurement dates and self-referential commentary ('no reader of this sentence was told') is editorial bloat that an agent does not need in order to invoke the tool.

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 an output schema present, return values need no explanation, and the description still covers auth tier, field availability, data-gap semantics, pagination via cursor, and the sibling directory boundary. Nothing an agent needs to call it correctly is missing.

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

Parameters3/5

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

Schema description coverage is 100%, so every parameter (mode, name, limit, cursor, country, delivers) is already documented in the schema with its declared-only semantics. The description adds only the top-level 'search by name' hint and sparsity framing, which is not parameter-level detail beyond 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 and resource ('The paid intelligence profile of a services firm') and enumerates the exact fields returned (SAP practice size, partner level, day rate, clients, LinkedIn URL). It also explicitly differentiates itself from the sibling search_firms ('a different, wider population'), so an agent can route without opening either schema.

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 concrete usage conditions: requires a Legend-tier subscriber key, search by name, and the alternative public directory is search_firms. It also carves out a hard exclusion (person-shaped fields never served at any tier). It stops short of a crisp 'use this instead of get_firm when...' rule against the closest sibling.

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

get_sap_client_profileOne SAP end-customer profile (Legend tier)A
Read-onlyIdempotent
Inspect

The full profile of one SAP end-customer — SAP footprint (products in use, modules known), analytics solutions, identity and evidence fields. Requires a subscriber API key, Legend tier or above. id comes verbatim from find_sap_clients.rows[].id.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesProfile id, verbatim from find_sap_clients.rows[].id.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare readOnly, idempotent, and non-destructive, so the description adds value by disclosing the API key/tier requirement and the source of the id. It does not contradict annotations and provides meaningful operational context beyond the schema.

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

Conciseness5/5

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

Two compact sentences. The first states the purpose and content; the second covers auth and id provenance. No filler or repetition of annotations.

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?

For a simple single-parameter retrieval tool with an output schema present, the description covers purpose, content, auth, and id provenance. It is sufficiently complete to guide correct invocation without needing to explain return values.

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 already covers the only parameter with 100% coverage. The description adds extra semantic value by explicitly stating that `id` comes verbatim from find_sap_clients.rows[].id, reinforcing correctness of parameter use beyond the schema's brief label.

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?

The description specifies a singular, concrete resource: the full profile of one SAP end-customer with listed content domains (SAP footprint, analytics, identity, evidence). It clearly distinguishes from the sibling find_sap_clients by referencing a specific id from that list.

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?

States the prerequisite (subscriber API key, Legend tier) and tells the agent that the id should come from find_sap_clients.rows[].id, implying the natural workflow: first list, then get detail. It does not explicitly mention when not to use alternatives, but the referential guidance provides sufficient usage direction.

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

get_studyRead a study (Consultant tier)A
Read-onlyIdempotent
Inspect

Read one Analytics Legends study BODY — the paid text behind list_studies' metadata. Requires a subscriber API key, Consultant tier or above. Bodies run to 38k words and exceed the 256 KiB response ceiling, so this tool serves STRUCTURE first: called without section it returns the section list and the introduction; pass section (a heading from that list, matched case-insensitively) to read one section. Find slugs and languages with list_studies.

ParametersJSON Schema
NameRequiredDescriptionDefault
langNoISO-639-1 language of the edition, e.g. en or fr. Defaults to en.en
slugYesStudy slug, verbatim from list_studies.rows[].slug.
sectionNoA section heading from a previous call's `sections` list. Omit to get the list.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.9/5.0
Behavior5/5

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

Beyond the annotations (readOnly, idempotent, non-destructive), the description discloses significant behavioral traits: a subscriber API key and Consultant tier requirement, the 256 KiB response ceiling, the structure-first behavior, case-insensitive section matching, and inclusion of the introduction. This is rich context useful for an agent.

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

Conciseness5/5

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

Every sentence earns its place: purpose, auth constraint, size limitation, usage mechanism, and pointer to list_studies. The description is front-loaded with the core action and then methodically explains the chunked reading approach without redundancy. Length is justified by the tool's complexity.

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?

Given the tool's complexity (auth tier, size limit, section-based reading) and the presence of an output schema, the description fully covers how to invoke it correctly: what to pass, what to expect, and where to get inputs. No critical usage aspect is missing.

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 baseline is 3. The description adds value by noting that `section` matching is case-insensitive and that omitting `section` returns not just the section list but also the introduction. It also clarifies that `slug` is verbatim from list_studies, reinforcing schema hints.

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?

The description opens with a specific verb+resource: 'Read one Analytics Legends study BODY — the paid text behind list_studies' metadata.' This clearly distinguishes it from sibling list_studies and other get_* tools by focusing on the full text body, not metadata or other entity types.

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

Usage Guidelines5/5

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

It explicitly tells when to use this tool vs list_studies: 'Find slugs and languages with list_studies.' It also provides a full usage pattern: call without `section` for the section list and introduction, then pass a section heading for one section. No exclusions are needed because the alternative is named.

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

list_firm_kindsList firm kinds with live countsA
Read-onlyIdempotent
Inspect

Breakdown of the published firm directory by organisation kind, with a live row count per kind. Use this before search_firms to know what the population actually is instead of guessing.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, covering the safety profile. The description adds useful behavioral detail: the counts are 'live', indicating freshness and that the data reflects the current state, which is not conveyed by annotations or schema.

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

Conciseness5/5

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

The description is two sentences, front-loaded with the core function and then a practical usage hint. Every word earns its place, with no redundancy or filler.

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?

Given the simple parameterless signature, rich annotations, and presence of an output schema, the description provides sufficient context for an agent to understand the tool's role and when to invoke it. It adds domain context (published directory, live counts) and usage guidance, making it complete for this tool's complexity.

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?

There are no parameters, so the description carries no parameter burden. The baseline for zero parameters is 4, and though the description doesn't add parameter-specific meaning, it explains the output grouping by organization kind, which helps set expectations for the result.

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?

The description clearly states the tool provides a breakdown of the published firm directory by organization kind, with live counts. It distinguishes itself from sibling tools like search_firms by focusing on population-level aggregation rather than individual firm lookup, and explicitly frames it as a precursor to searching.

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?

The description explicitly says to use this tool before search_firms, providing clear context and a naming the alternative. It lacks explicit 'when not to use' exclusions, but the guidance to use it as a preliminary step is direct and helpful.

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

list_freelance_platformsList the CV/profile platforms a consultant can sign up onA
Read-onlyIdempotent
Inspect

The subset of the published directory where a consultant can CREATE A PROFILE — freelance marketplaces, job boards with candidate profiles, talent platforms and expert networks — each with its signup URL, an editorial confidence grade and the date it was assessed. This answers the entering-contractor's first practical question ('where do I register?') in one call. Everything here is also in search_firms — this tool adds the platform fields and the filter, never a wider population. signup_url is the platform's own page: it was verified on assessed_at, and a platform absent here is not proven to refuse signups — it is unassessed or unpublished.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax rows (hard cap 50).
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.
countryNoISO-3166-1 alpha-2 country code, e.g. DE, FR, CH.
platform_typeNoRestrict to one platform type, lowercase snake_case. The live vocabulary with counts is `_meta.available_platform_types` on every response — a well-formed unknown value returns no rows, it never widens the result.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare read-only, non-destructive, and idempotent behavior, so the description does not need to re-cover those. It does add meaningful semantics: signup_url is verified on assessed_at, and a platform absent here is not proof of refusal—it is merely unassessed or unpublished. These boundary details would be hard to infer from the schema alone.

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

Conciseness5/5

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

The main purpose is front-loaded, and every sentence carries signal: what the list contains, which question it answers, how it relates to search_firms, and how URL verification/absence should be interpreted. There is no filler or redundancy with the title.

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?

The description is complete for this read-only list tool: it clarifies scope, a reliable sibling relationship, verification meaning of the returned URL, and absence semantics. With four optional parameters all documented in the schema and read-only annotations present, nothing critical is missing for correct invocation.

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

Parameters3/5

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

Schema description coverage is 100%, and every parameter already has a focused description including cursor rules, country format, and platform_type behavior. The tool description adds little parameter-level meaning beyond clarifying that the tool applies a platform-type filter, so the appropriate baseline is 3.

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?

The description names a specific verb (list) and resource (freelance platforms where a consultant can create a profile) and gives concrete examples of the included platform kinds. It also clearly distinguishes itself from search_firms by stating this is a narrower subset that adds platform fields, so an agent can tell it apart from sibling tools.

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

Usage Guidelines5/5

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

The description explicitly positions this tool as the one answering 'where do I register?' and names search_firms as the broader alternative, clarifying that this tool never returns a wider population. That gives an agent a direct routing decision: use this when platform signup fields are the goal, and search_firms for the broader directory.

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

list_sap_modulesSAP AI & analytics module taxonomyA
Read-onlyIdempotent
Inspect

The canonical SAP module/product taxonomy Analytics Legends classifies against (codes and EN/FR labels by category). Use it to normalise a user's loose product wording — 'SAC', 'Analytics Cloud', 'Datasphere' — onto the codes the other tools filter on.

ParametersJSON Schema
NameRequiredDescriptionDefault
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.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.2/5.0
Behavior4/5

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

Annotations already cover the safety profile (readOnlyHint, idempotentHint, destructiveHint=false, closed-world), so the bar is lower. The description still adds useful content semantics — bilingual EN/FR labels organised by category and the canonical code set — that the annotations do not convey.

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

Conciseness5/5

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

Two sentences, front-loaded with what the resource is before explaining why to call it. No filler text; every clause carries information.

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

Completeness4/5

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

An output schema exists, so return-value detail is not required, and the parameters are self-documenting. Purpose and use case are covered; the only minor omission is any note about pagination behaviour across pages, which the cursor description largely handles.

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

Parameters3/5

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

Schema description coverage is 100%, so limit, query and cursor are fully documented in the schema itself, including the substring-per-word matching rule and cursor reuse constraints. The description adds no additional parameter meaning, so the baseline 3 applies.

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 and resource ('canonical SAP module/product taxonomy') and specifies what it contains ('codes and EN/FR labels by category'), which is more precise than the name alone. It also positions itself relative to the other tools by noting these are 'the codes the other tools filter on', so an agent can tell it apart from look-alikes such as list_firm_kinds and list_freelance_platforms.

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-to-use with concrete examples ('SAC', 'Analytics Cloud', 'Datasphere') and the goal of normalising loose wording onto filter codes. There is no when-not guidance or named alternative tool, which keeps it short of a 5.

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

list_studiesList the deep-research studies (metadata only)A
Read-onlyIdempotent
Inspect

List the Analytics Legends deep-research studies with their edition, as-of date, audience, word count and canonical URL. METADATA ONLY: study bodies are a paid Consultant-tier deliverable, served by get_study on this same endpoint with a subscriber key. Use this to tell a reader that a study exists and where to read it. ONE ROW IS ONE LANGUAGE EDITION, NOT ONE STUDY: each study is published in every language it has been translated into, so _meta.tranche_total_row_count counts editions and _meta.distinct_studies counts the works. _meta.available_languages gives the live per-language counts; each row carries its editions list. Pass lang to get one row per study.

ParametersJSON Schema
NameRequiredDescriptionDefault
langNoISO-639-1 language of the EDITION to list, e.g. en, fr or de — each study is published as one row per language. Omit it to see every edition of every study; the live list of languages actually present comes back as `_meta.available_languages` on every response. A well-formed code the corpus does not hold returns 0 rows.
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.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.8/5.0
Behavior5/5

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

The description discloses read-only behavior, the one-row-per-language-edition semantics, the `_meta` fields, and the outcome for invalid language codes (0 rows). These details go beyond the annotations and do not contradict `readOnlyHint`, `idempotentHint`, or `destructiveHint`.

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

Conciseness4/5

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

The description is dense and front-loaded with the purpose, but it repeats the language-edition rule twice ('ONE ROW IS ONE LANGUAGE EDITION' and later 'each row carries its editions list'). This minor redundancy prevents a perfect score.

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?

Given the presence of an output schema, the description appropriately omits return-value details but includes essential operational context: metadata-only nature, pagination, `_meta` fields, and the relationship to `get_study`. It is complete for the intended use.

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 descriptions fully cover all four parameters, so the baseline is 3. The description adds value by reinforcing the meaning of `lang` (one row per language) and the cursor's dependency on unchanged filters, giving extra context beyond 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?

The description clearly states the tool's function: listing deep-research studies with metadata fields (edition, as-of date, audience, word count, URL). It explicitly contrasts with `get_study` for study bodies, making its scope distinct from sibling tools.

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

Usage Guidelines5/5

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

It plainly says 'METADATA ONLY,' directs to `get_study` for bodies, and explains the use case ('tell a reader that a study exists and where to read it'). It also covers pagination, language-edition behavior, and filter constraints, leaving no ambiguity about when and how to use it.

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

query_knowledge_graphTraverse the learning knowledge graphA
Read-onlyIdempotent
Inspect

The RELATIONS between the platform's teaching objects — which Academy module teaches which concept, which study covers which module, what a concept relates to. THIS IS THE ONLY TOOL ON THIS SERVER THAT SERVES EDGES; the others serve rows. Ask it what connects to what, not what exists. SCOPE, AND IT IS NARROWER THAN 'the knowledge graph': it carries four node types — concept, module, study, vendor — and every edge whose BOTH endpoints are one of them. The whole graph holds twelve node types; the eight it does not carry are each either served by their own tool or named as not served at all, and _meta.excluded_node_types says which per type (consultant data is served at NO tier), so a missing type is a documented boundary and never a silent gap. Call it with node_id (e.g. module:M178, concept:C001, study:ai-impact-2026-EN) to walk one node's neighbourhood; with node_type and/or query to find a node id first. edge_type and direction narrow a walk. Read _meta.available_edge_types — computed from the served projection on every call — before assuming an edge type exists.

ParametersJSON Schema
NameRequiredDescriptionDefault
langNoISO-639-1 language for the LABELS — en, fr or de. Defaults to en. The fallback is declared and never silent: the language asked for, then English, then French, and `_meta.label_language_coverage` says on how many nodes each language is actually filled. Concept and module titles carry French; German arrives as the corpus is translated, and an untranslated node falls back rather than being hidden.
limitNoMax rows (hard cap 50).
queryNoCase-insensitive substring of a node's label. Applies to the NODE listing, not to a walk. Matched against the label SERVED, so it follows `lang`.
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.
node_idNoFully-qualified node id, `<type>:<id>` — `module:M178`, `concept:C001`, `study:ai-impact-2026-EN`, `vendor:alteryx`. With it, rows are that node's EDGES (one row per neighbour). Without it, rows are NODES.
directionNoWhich side of the edge `node_id` must sit on. Default `both`. Ignored without `node_id`, and the response says so rather than pretending it applied.
edge_typeNoRestrict a walk to one relation. The authority is `_meta.available_edge_types`, computed on every call — no list is kept here: a four-name list once stood here while the projection served five, then a five-name list while it served six (`broader`).
node_typeNoRestrict to one carried node type: concept · module · study · vendor. Read `_meta.available_node_types`.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A5/5.0
Behavior5/5

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

Beyond the read-only annotations, it discloses important boundaries: the four carried node types, eight excluded node types documented in `_meta.excluded_node_types`, consultant data served at no tier, and `_meta.available_edge_types` as the runtime authority. It also explains language fallback behavior and cursor reuse constraints, which are behavioral context not present in annotations.

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

Conciseness5/5

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

The description is long but appropriately so for a complex, multi-parameter traversal tool, and it is front-loaded with the core edge-vs-row distinction. Every major sentence appears to carry operational information, including scope boundaries, invocation modes, and dynamic meta fields.

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?

Given the tool's complexity, 8 parameters, full schema descriptions, annotations, and an output schema, the description is complete enough for correct invocation. It covers purpose, scope, alternatives, edge/node distinction, parameter intent, pagination, language fallback, and dynamic metadata without needing to describe return values already covered by the output schema.

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

Parameters5/5

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

Although schema coverage is 100%, the description adds substantial meaning: node_id examples and its role in switching rows from nodes to edges, direction being ignored without node_id, query applying to node listings rather than walks, and lang affecting served labels with declared fallback. These details go well beyond the schema's field descriptions.

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?

It states a specific verb and resource — traversing the learning knowledge graph for relations — and explicitly distinguishes this as the only tool on the server that serves edges rather than rows. The scope is narrowed to four node types and the contrast with 'what exists' tools makes the purpose unambiguous.

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

Usage Guidelines5/5

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

It tells the agent exactly when to use it: 'Ask it what connects to what, not what exists,' and contrasts it with sibling node-serving tools. It also explains the two invocation modes clearly — walk via node_id or discover a node id first via node_type and/or query — and notes how edge_type and direction narrow a walk.

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

search_conceptsSearch the SAP AI & analytics concept encyclopaediaA
Read-onlyIdempotent
Inspect

Search the SAP AI & analytics concept encyclopaedia — the vocabulary of the stack, written for practitioners. Returns slug, title, category, level, tags and the editor's summary. level is GRADED on every active row since 2026-09-18 (the CHECK constraint accepted only the legacy vocabulary OR NULL, so the loader wrote NULL rather than fail; 108 of 330 were blank). A null, if one ever returns, means 'not graded', never 'Beginner'. These are the same fields get_concept returns for ONE slug. The card BODY (cheat sheet, glossary, pro tip and the four analysis tables) is Consultant-tier: call get_concept_card. Why-it-matters and key points are NOT served by this endpoint either, but they are published in full on the concept page at citation_url — follow the URL for those, a plan buys the body, not them.

ParametersJSON Schema
NameRequiredDescriptionDefault
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.
categoryNoConcept category, matched case-insensitively as an exact value OR a prefix — so category:"datasphere" reaches 'Datasphere Core'. The values are long human labels, not codes. DO NOT GUESS THEM FROM THIS TEXT: the live vocabulary with a row count per label comes back as `_meta.available_categories` on EVERY call, including a call that matched nothing. A list written here would say 14 labels with 2026-07-30 counts; the corpus holds 15 today, and five of those counts have moved. Read the envelope, not the prose.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), and the description layers on genuinely non-obvious behavior: level is now graded on every active row, a null means 'not graded' and never 'Beginner', and the body/why-it-matters content is tier-gated or served elsewhere. This is behavioral context an agent cannot infer from annotations or schema.

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

Conciseness4/5

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

Front-loads what it returns, then constraint caveats, then sibling alternatives. Mostly efficient, but the parenthetical backstory (CHECK constraint, '108 of 330 were blank') is heavier than needed to convey that null means 'not graded'.

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 an output schema present, the description needn't explain return shape, yet it still covers pagination semantics, tier gating, the null-level edge case, and where the non-served fields live. Nothing an agent needs to invoke this correctly is missing.

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 baseline is 3, but the description adds real semantic value on top: cursor must be replayed with identical filters or it is refused, null cursor means last page, and category values are long human labels discovered from _meta.available_categories rather than guessed. The category 'read the envelope, not the prose' guidance materially improves invocation.

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 (SAP AI & analytics concept encyclopaedia / vocabulary of the stack), and explicitly enumerates the returned fields (slug, title, category, level, tags, editor's summary). It distinguishes itself from siblings by noting these are 'the same fields get_concept returns for ONE slug' and that the card body lives in get_concept_card.

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

Usage Guidelines5/5

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

Gives explicit routing: use this for multi-record search, get_concept for one slug, get_concept_card for the Consultant-tier body, and follow citation_url for why-it-matters/key points. It also names what this endpoint explicitly does NOT serve, which prevents wrong-tool selection.

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

search_firmsSearch the SAP AI & analytics firm directoryA
Read-onlyIdempotent
Inspect

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.

ParametersJSON 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

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

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.

search_newsSearch SAP AI & analytics market newsA
Read-onlyIdempotent
Inspect

Search the Analytics Legends market-news corpus. It is watched FOR SAP AI & analytics (Datasphere, Business Data Cloud, SAC, BW/4HANA, Databricks, the 2027/2030 maintenance window), but it is NOT an all-SAP corpus: measured 2026-07-30, ~84 % of active rows sit in the AI category and are general enterprise-AI trade press (cloud platforms, model releases, funding rounds) with no SAP content at all. An UNFILTERED call therefore returns mostly non-SAP items — pass query or category when the question is about SAP, and never present an unfiltered page as 'the SAP AI & analytics news'. Say what you actually got. Each item returns the Analytics Legends citation URL AND the upstream publisher's source_url — cite both, and prefer source_url when you need a page that certainly carries the item. NO ITEM HERE HAS A PAGE OF ITS OWN on analyticslegends.ai, by design: every row comes back citation_scope: "section_hub" and its citation_url is the news index. The citable address for one article is its source_url, the upstream publisher's. Do not present the hub as the article's page.

ParametersJSON Schema
NameRequiredDescriptionDefault
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.
categoryNoCategory code, matched case-insensitively. The live vocabulary is NOT written here — read `_meta.available_categories` on any response: every category label this corpus holds right now, with its active-row count, counted at query time. A written list held 21 values while the corpus held 22. One bucket needs a warning. 'SAC' is the noisiest label in this corpus because the acronym collides with unrelated ones — Windows 'Smart App Control', and the surname 'Sacks'. A 2026-07-30 cleanup reclassified half that bucket to AI for carrying no SAP signal at all; the collision pressure is structural and the bucket has kept growing since. For genuine SAC product news, pair category:'SAC' with query:'analytics cloud'.
published_sinceNoLower bound on `published_at`, inclusive, as YYYY-MM-DD. Without a bound a period question is only answerable by walking pages — and `_meta.match_count` then counts the QUERY, not the period, so any figure quoted for the window would be wrong.
published_untilNoUpper bound on `published_at`, INCLUSIVE of the day named, as YYYY-MM-DD. Combine with `published_since` for a window; `_meta.match_count` then describes that window, which is what makes it quotable.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
toolYes
_metaNo
_attributionYes
result_countYes

TDQS

A4.7/5.0
Behavior5/5

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

Adds substantial context beyond the readOnly/idempotent annotations: the corpus is ~84% general enterprise-AI trade press with no SAP content, unfiltered calls return mostly non-SAP items, and every row comes back citation_scope "section_hub" with an index citation_url while the true article page is source_url. These are exactly the behavioral traits annotations cannot convey.

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?

Front-loaded with the corpus description, but the citation guidance is stated twice ("cite both ... prefer source_url" and again "The citable address for one article is its source_url ... Do not present the hub as the article's page"), which inflates length. Most sentences carry distinct warnings, but the redundancy and overall length keep it from being tight.

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 an output schema present and 100% schema coverage, the description still covers the non-obvious gaps: corpus composition, filtering necessity, citation semantics, and cursor reuse. An agent has everything needed to call the tool correctly and interpret results honestly.

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, but the description adds real meaning: it explains why the category vocabulary is not written out (read _meta.available_categories) and warns that 'SAC' is structurally noisy due to acronym collisions, with a workaround (pair with query:'analytics cloud'). It also justifies why published_since/until matter for match_count correctness.

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?

Opens with a specific verb+resource ("Search the Analytics Legends market-news corpus") and immediately scopes it (SAP AI & analytics topics, not an all-SAP corpus). An agent knows exactly what this tool returns versus siblings like search_firms or search_concepts, which are entity searches rather than news retrieval.

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

Usage Guidelines5/5

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

Gives explicit when-to and when-not-to guidance: "pass `query` or `category` when the question is about SAP, and never present an unfiltered page as 'the SAP AI & analytics news'." It also states the correct pattern for cursors (pass back with the SAME filters) and for period questions (use published_since/until so match_count is quotable), and prescribes citation behavior.

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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool update
    • Changedget_day_rate_benchmark1 field changed
      • changedInput schema / properties / specialisation / description
        Previous value: -"One of the codes the aggregate actually holds — analytics_all, bw, bw4hana, bw4hana_sac, sac, datasphere (2026-08-09; the live list comes back as `_meta.available_specialisations` on every call). They are NOT evenly spread across countries: DE holds analytics_all only, at three seniorities, and datasphere exists for FR alone, as a median with confidence 'low'. There is no bdc row and no joule row in THIS aggregate — but the platform does price BDC: `find_opportunities` carries a panel-inferred band on the live postings, €1,000–€1,400/day P25–P75 for Senior BDC on 52 German postings (2026-08-10). A code this aggregate does not hold returns 0 rows and the available codes; it never widens to a neighbouring band, and 0 here does not mean the platform is silent."New value: +"A specialisation code, matched exactly. The live list is `_meta.available_specialisations` on every call — no list is kept here, because a written one went stale. Codes are NOT evenly spread across countries: read the rows a country filter returns. A code this aggregate does not hold (bdc, joule) returns 0 rows and the available codes; it never widens to a neighbouring band. 0 rows here is an absence in THIS aggregate only — see the tool description for what the website's subscriber band covers and where no rate exists at any tier."
  2. 2 tool updates
    • Changedfind_partner_candidates2 fields changed
      • changedInput schema / properties / kind / description
        Previous value: -"Restrict to one provider kind (placement agencies, ESN/SI practices, Big-4, SAP partners, platforms). Communities, schools and public bodies are never in this population."New value: +"Restrict to one provider kind (placement agencies, ESN/SI practices, Big-4, SAP partners, platforms). SAP's own entities, communities, schools and public bodies are never in this population."
      • changedInput schema / properties / kind / enum
        Previous value: -[
        -  "agency_placement",
        -  "professional_services_esn",
        -  "professional_services_big4",
        -  "vendor_partner",
        -  "vendor_sap",
        -  "platform_marketplace",
        -  "other"
        -]New value: +[
        +  "agency_placement",
        +  "professional_services_esn",
        +  "professional_services_big4",
        +  "vendor_partner",
        +  "platform_marketplace",
        +  "other"
        +]
    • Changedget_firm_intel1 field changed
      • changedInput schema / properties / name / description
        Previous value: -"Firm name, matched case-insensitively. Omit to browse the corpus by data completeness."New value: +"Firm name, matched case-insensitively; an exact name ranks first, then a name starting with it, then the other matches. Omit to browse the corpus by data completeness."
  3. 1 tool update
    • Changedquery_knowledge_graph1 field changed
      • changedInput schema / properties / edge_type / description
        Previous value: -"Restrict a walk to one relation. The served projection carries FIVE — teaches · taught_by · covers · related · mentions — and this list is a HINT, not the authority: read `_meta.available_edge_types`, computed on every call. A four-name list stood here while the projection served five, so `mentions` was reachable and undocumented."New value: +"Restrict a walk to one relation. The authority is `_meta.available_edge_types`, computed on every call — no list is kept here: a four-name list once stood here while the projection served five, then a five-name list while it served six (`broader`)."
  4. 4 tool updates
    • Changedcount_firms_by2 fields changed
      • changedInput schema / properties / by / enum
        Previous value: -[
        -  "country",
        -  "kind",
        -  "module",
        -  "sap_signal_band"
        -]New value: +[
        +  "country",
        +  "region",
        +  "kind",
        +  "module",
        +  "sap_signal_band"
        +]
      • 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"
        +}
    • Addedfind_partner_candidates
    • Addedget_expansion_brief
    • Changedsearch_firms1 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"
        +}
  5. 1 tool update
    • Changedget_day_rate_benchmark5 fields changed
      • addedOutput schema / properties / rows / items / properties / client_market
        Added value: +{
        +  "description": "india_domestic (INR, Indian client) | eu_uk_remote (EUR, India-based contractor billed to an EU/UK client). Set on the India rows only.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_max_inr
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_max + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_median_inr
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_median + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_min_inr
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_min + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • changedOutput schema / properties / rows / items / properties / source_type / description
        Previous value: -"primary_agency_guide | primary_job_ads | aggregator | marketing"New value: +"primary_agency_guide | primary_job_ads | primary_salary_survey | aggregator | marketing"
  6. 1 tool update
    • Changedget_day_rate_benchmark1 field changed
      • changedOutput schema / properties / rows / items / properties / indicative / description
        Previous value: -"true = orientation only: the source is a proxy (non-SAP BI band, generic ERP, agency monthly contract or marketing average). Never quote it as the SAP analytics rate of its country."New value: +"true = orientation only: the source is a proxy (non-SAP BI band, generic ERP, agency monthly contract or marketing average). Never quote it as the SAP AI & analytics rate of its country."
  7. 1 tool update
    • Changedsearch_firms1 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"
        +]
  8. 1 tool update
    • Changedget_day_rate_benchmark21 fields changed
      • addedOutput schema / properties / rows / items / properties / accessed
        Added value: +{
        +  "description": "date the source was read (YYYY-MM-DD)",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / conversion
        Added value: +{
        +  "description": "how an hourly or monthly source figure was turned into a day rate",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_max_aud
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_max + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_max_hkd
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_max + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_max_jpy
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_max + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_max_nzd
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_max + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_max_sgd
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_max + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_median_aud
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_median + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_median_hkd
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_median + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_median_jpy
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_median + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_median_nzd
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_median + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_median_sgd
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_median + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_min_aud
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_min + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_min_hkd
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_min + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_min_jpy
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_min + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_min_nzd
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_min + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_min_sgd
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_min + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / indicative
        Added value: +{
        +  "description": "true = orientation only: the source is a proxy (non-SAP BI band, generic ERP, agency monthly contract or marketing average). Never quote it as the SAP analytics rate of its country.",
        +  "type": [
        +    "boolean",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / sap_scope
        Added value: +{
        +  "description": "what the source actually measures: sap_analytics | sap_general | erp_general | bi_non_sap",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / source_period
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / source_type
        Added value: +{
        +  "description": "primary_agency_guide | primary_job_ads | aggregator | marketing",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
  9. 2 tool updates
    • Changedfind_opportunities6 fields changed
      • changedInput schema / properties / lang / description
        Previous value: -"Reading language for the TITLE — 'EN' (default), 'FR' or 'DE'. This is a RENDERING choice, never a filter: it changes which string `title` carries, never which rows come back. Read `title_lang` on every row for the language actually served: it differs from what you asked for exactly when that translation does not exist (FR covers 1,492 of 1,588 site-radar rows, DE 1,373 — measured 2026-09-04), and the verbatim is served instead, labelled with the language the harvest chain measured. `source_lang` always carries the language the ADVERTISER wrote in, translated or not. The promoted leg has no translated columns at all: its rows ignore this argument and say so with `title_lang: null` — see `_meta.untranslated_leg`."New value: +"Reading language for the TITLE — 'EN' (default), 'FR' or 'DE'. This is a RENDERING choice, never a filter: it changes which string `title` carries, never which rows come back. Read `title_lang` on every row for the language actually served: it differs from what you asked for exactly when that translation does not exist (the live FR/DE coverage is counted on every response in `_meta.untranslated_leg`), and the verbatim is served instead, labelled with the language the harvest chain measured. `source_lang` always carries the language the ADVERTISER wrote in, translated or not. The promoted leg has no translated columns at all: its rows ignore this argument and say so with `title_lang: null` — see `_meta.untranslated_leg`."
      • addedOutput schema / properties / rows / items / properties / canonical_id
        Added value: +{
        +  "description": "Set when the site folded this posting into another one it judged identical: `citation_url` is then the page of THAT posting (`canonical_id`). When both are matched, only the canonical row is served and `_meta.duplicates_folded` counts the fold. Null otherwise.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / salary_min / description
        Added value: +"The posting's own advertised salary, YEARLY (see `salary_period`). Null when none was published, and null when the published amount cannot be a yearly salary — `salary_withheld_reason` then says why."
      • addedOutput schema / properties / rows / items / properties / salary_period
        Added value: +{
        +  "description": "`year` when salary_min/salary_max are served. Null otherwise — never a guessed unit.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / salary_withheld_reason
        Added value: +{
        +  "description": "Why a published salary was NOT served: `below_annual_floor` (the low end is below any yearly salary in that currency — a monthly or daily figure filed as a salary) or `board_estimate` (the job board's own min = max estimate, not the employer's). The amount is withheld, never converted. Null when nothing was withheld.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • changedOutput schema / properties / rows / items / properties / title_lang / description
        Previous value: -"ISO-639-1 language the SERVED `title` is written in. READ IT ON EVERY ROW: it is what you asked for in `lang` when that translation exists, and the advertiser's own language when it does not (FR covers 1,492 of 1,588 site-radar rows, DE 1,373 — measured 2026-09-04). Null when the harvest chain could not decide, and on every promoted-leg row, which carries no translated column at all. ⚠️ THIS FIELD CHANGED REFERENT ON 2026-09-04: until then it named the language the ADVERTISER wrote in — that fact now lives in `source_lang`. The two coincided while no translation was served and diverge from the first one that is."New value: +"ISO-639-1 language the SERVED `title` is written in. READ IT ON EVERY ROW: it is what you asked for in `lang` when that translation exists, and the advertiser's own language when it does not (the live FR/DE coverage is counted on every response in `_meta.untranslated_leg`). Null when the harvest chain could not decide — on some site-radar rows too, counted in `_meta.untranslated_radar_rows` — and on every promoted-leg row, which carries no translated column at all. ⚠️ THIS FIELD CHANGED REFERENT ON 2026-09-04: until then it named the language the ADVERTISER wrote in — that fact now lives in `source_lang`. The two coincided while no translation was served and diverge from the first one that is."
    • Changedquery_knowledge_graph1 field changed
      • changedOutput schema / properties / rows / items / properties / direction / description
        Previous value: -"`out` = node_id → neighbour, `in` = neighbour → node_id. Walk rows only."New value: +"`out` = node_id → neighbour, `in` = neighbour → node_id, `both` = the relation is stored in both directions (served once — see `_meta.symmetric_edges_folded`). Walk rows only."
  10. 1 tool update
    • Changedfind_opportunities1 field changed
      • changedInput schema / properties / employment_type / description
        Previous value: -"Restrict to one engagement type. THE RADAR IS MOSTLY PERMANENT, so a freelance or contract question answered off an unfiltered page is answered with salaried jobs. For the actual split, make the filtered call and read `_meta.tranche_total_row_count` — it is counted at query time. The promoted leg declared NO contract_type until 2026-08-28 and now declares one on most of its rows, so a value here no longer drops that leg wholesale — only the rows still silent. THOSE ROWS ARE NOT A FOURTH TYPE AND NOT PERMANENT ONES: `_meta.available_employment_types` counts only what declares, and `_meta.employment_type_undeclared` carries the rest, so the two together are the population and either alone is not. Read both before quoting a mix."New value: +"Restrict to one engagement type. THE RADAR IS MOSTLY PERMANENT, so a freelance or contract question answered off an unfiltered page is answered with salaried jobs. For the actual split, make the filtered call and read `_meta.tranche_total_row_count` — it is counted at query time. The promoted leg declared NO contract_type until 2026-08-28 and now declares one on most of its rows, so a value here no longer drops that leg wholesale — only the rows still silent. THOSE ROWS ARE NOT A FOURTH TYPE AND NOT PERMANENT ONES: `_meta.available_employment_types` counts only what declares, and `_meta.employment_type_undeclared` carries the rest, so the two together are the rows THIS call read (`_meta.employment_mix_rows_read` — a window, NOT the tranche: `_meta.tranche_total_row_count` is the population) and either alone is not even that. Read both before quoting a mix, and quote the window with it."

Related MCP Connectors

Related MCP Servers

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.