Skip to main content
Glama

Analytics Legends — SAP Analytics Intelligence

Server Details

AI agent for SAP analytics: firms, day rates, contract radar, news, concepts, studies

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 43 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 20 tools

Disambiguation4/5

Most tools have clearly distinct scopes (firm directory vs SAP end-customers vs academy vs studies vs concepts vs news), and the descriptions explicitly police the boundaries. Real overlap remains at the edges: list_firm_kinds and count_firms_by both return breakdowns/counts over the same firm directory, and list_freelance_platforms is a documented subset of search_firms, so misselection is possible though the text steers the agent correctly.

Naming Consistency4/5

Every name is snake_case verb_noun and readable at a glance. The only wrinkle is that the same retrieval action is split across find_ (find_academy_modules, find_opportunities, find_sap_clients) and search_ (search_firms, search_concepts, search_news) with no stated rule, plus the slightly awkward trailing 'by' in count_firms_by.

Tool Count4/5

20 tools is at the heavy end of the acceptable band, but this server spans many genuinely separate content verticals (firms, clients, academy, studies, concepts, news, opportunities, rates, knowledge graph) and each tool maps to one vertical/verb. Nothing looks padded, but the surface is broad enough to strain a caller's tool selection.

Completeness4/5

Search+fetch pairs exist for nearly every entity (firms, concepts, academy modules, studies, SAP clients), plus aggregation, taxonomy and graph traversal, covering a read-only content platform well. Minor gaps: no per-item detail fetch for opportunities or news rows (only list/search with citation URLs), and consultant/vendor data is intentionally absent at all tiers.

Available Tools

20 tools
count_firms_byCount the firm directory by country, 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 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).
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.9/5.0
Behavior5/5

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

Annotations already provide readOnlyHint, idempotentHint, and destructiveHint, lowering the burden. The description adds valuable behavior beyond those hints: null buckets are real and served rather than hidden, counts reflect the full population, and filters match search_firms exactly. This prevents an agent from misinterpreting missing facets.

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?

Three sentences, each earning its place: purpose, usage rationale, and a crucial data-completeness behavior. It is front-loaded with the core action and keeps the details relevant rather than padding.

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 read-only counting tool with a rich input schema and an output schema present, the description covers the essential context: what it counts, how it relates to search_firms, when to prefer it, and how null buckets behave. Nothing critical 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 schema already documents each parameter well. The description still adds useful relational context: the filters are the same ones search_firms accepts, and counting a filtered slice is as easy as counting the whole. This helps an agent reuse knowledge of a sibling tool.

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: 'Answer a COUNTING question about the published firm directory'. It names the four facets (country, kind, module, SAP signal band) and explicitly contrasts with search_firms, so an agent can tell this tool from its sibling without opening schemas.

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 gives explicit when-to-use guidance: use this instead of paging through search_firms and tallying rows. It even quantifies why, citing hundreds of calls and megabytes of rows, which makes the trade-off concrete and actionable.

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: pass _meta.next_cursor back as cursor with the same filters until it is null. 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.9/5.0
Behavior5/5

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

Beyond the readOnly/idempotent annotations, the description discloses default ordering, pagination behavior via _meta.next_cursor, the live count semantics of tranche_total_row_count, that status and is_preview are served but not filterable, and the measured discrepancy between them. This is substantial behavioral context that the annotations alone do not provide.

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 core search purpose, and the critical disambiguation from list_sap_modules appears early. Some pricing/subscription detail is longer than strictly necessary for invoking this endpoint, but it supports correct routing to get_academy_module.

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 covers filtering, pagination, ordering, live facet counts, the distinction from sibling tools, access limitations, and count semantics. With a rich output schema present, no essential calling context is missing for an agent to use this tool correctly.

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 crucial parameter semantics: query words must all appear as substrings, track accepts slug or English name but deliberately not numeric track_id, and cursor must be passed back with identical filters or it is refused. These details are not inferable from the schema alone.

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 ('Search'), a resource (the Academy training catalogue), and the available facets (track, level, free text). It explicitly distinguishes it from list_sap_modules, so an agent can select between the two 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?

The description gives explicit guidance on when to use this tool versus list_sap_modules and get_academy_module, noting the product taxonomy and the subscriber-only module body respectively. It also states clearly that certification questions have no answer here, preventing misuse.

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 — the same population a human browses on /opportunities/, where each posting has its own prerendered page. 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 EVERY active row of that leg — an invariant held since 2026-07-31, not a snapshot). 🔴 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.2/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, closed-world), and the description still adds substantial undisclosed behavior: most radar rows have `source_url: null` and `application_link: "members_only"` (the gated Consultant-tier field), `rate_band` is null on every row with or without a key, cursors are refused when filters change, and the promoted leg's schema shape changed on a dated cutover. That is exactly the kind of context 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?

The purpose is front-loaded, but the body runs to roughly a thousand words with repeated injunctions ("read the `_meta` on your own response", "never from a figure quoted in this text") and historical churn narratives dated 2026-07-30/08-28/08-29 that do not help an agent decide or invoke. Much of it duplicates the already-verbose parameter descriptions.

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 an eight-parameter, two-leg search tool with gated fields and editorial rate bands, the definition covers population, filtering, gating, null-field behavior and pagination thoroughly. With an output schema present, return values need not be explained, so nothing material 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 schema itself is already extremely detailed, so the baseline is 3. The description mostly restates schema semantics (lang as a rendering choice, country applying to both legs, remote_mode setting undeclared rows aside) rather than adding parameter-level meaning the schema lacks.

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 opening states a specific verb and resource — searching every SAP contract and permanent-role posting that Analytics Legends publishes publicly — and scopes it to the anonymous visitor population behind /opportunities/. It is clearly distinguishable from siblings like search_firms, get_day_rate_benchmark or search_news, which address different corpora.

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 strong conditional guidance: read `employment_type` before treating this as a contract market, use the argument to filter, expect `rate_band` to be null everywhere, and never quote bands across postings. It does not, however, name a sibling tool as the alternative when this tool is the wrong one, so routing 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.

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 ~550 of ~21k rows), 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.9/5.0
Behavior5/5

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

Although annotations already mark readOnlyHint, idempotentHint, and destructiveHint, the description adds key behavior beyond those flags: the dataset is gated behind a subscriber key/Legend tier; verification status is NEVER silently filtered and appears as columns; product filter drops rows meaning 'not detected' rather than 'does not use'; cursor must be passed with the SAME filter arguments and changing filters refuses the cursor. This gives an agent safety-relevant and semantic behavior that annotations alone wouldn't 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?

The description is information-dense but every clause earns its place: the corpus distinction, access tier, verification semantics, product detection semantics, and cursor behavior are all core to correct invocation. There is no fluff or repetition of schema content; it is front-loaded with the end-customer vs firm distinction, which is the most important routing signal.

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 has an output schema, six optional params, and a sibling set that includes get_sap_client_profile and search_firms, this description fully disambiguates where and how to use it. It covers access requirements, result semantics, filtering semantics, and pagination constraints. Nothing critical an agent needs to know before calling 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 description coverage is 100%, so the schema already documents every parameter. The description adds value beyond the schema by explaining the detection semantics of the product enum, the 'never silently filtered' verification columns, and the cursor's same-filter requirement. It does not repeat schema details, so a 4 is appropriate; a 5 would require even more domain-specific parameter nuance beyond what the schema gives.

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 explicitly identifies the corpus (SAP end-customers), contrasts it with the sibling tool search_firms (services firms), and highlights the Legend tier requirement. It also says exactly what 'product' filtering means in terms of detection flags, so an agent can distinguish this tool from get_sap_client_profile and search_firms.

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 first sentence explicitly contrasts with search_firms: end-customers vs firms that sell services. It also explains the paid subscriber key / Legend tier context, tells the agent that verification status is always served and not silently filtered, and describes detection semantics so the agent can decide when to use this versus more detailed profile tools. This is strong when/when-not guidance.

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.
specialisationNoOne 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.

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 readOnlyHint/idempotent/destructive=false, so safety is covered. The description adds real behavioral context beyond that: the aggregate is small and secondary-source only, sample_size is null on most rows, and no paid tier withholds data. The 'frozen pair' digression and the argument about deliberately omitting counts are self-indulgent rather than informative, but they do not contradict 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.

Conciseness2/5

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

The first sentence is front-loaded and useful, but the body sprawls across many paragraphs of internal editorializing: a frozen statistic debunked at length, meta-commentary about why counts are not written, and repeated restatements of the same aggregate-vs-panel distinction. A large fraction of the text does not help an agent call the tool.

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?

With an output schema present, return values need not be explained, and the description instead covers the genuinely tricky parts: cross-tier boundaries, empty-cell semantics, currency-per-row, and _meta fields to consult. It is arguably over-complete on tangential matters but nothing an agent needs to interpret results 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 schema already carries detailed semantics per parameter (country code format, seniority examples, the full specialisation code list and its caveats). The description adds mainly the empty-result interpretation, which is usage guidance rather than parameter meaning. Baseline 3 is appropriate.

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 names a specific verb+resource+scope: the public day-rate aggregate with min/max daily rate by country, specialisation and seniority, including exact field names. It also explicitly distinguishes itself from the sibling find_opportunities (which carries rate_band). However, the signal is buried under several paragraphs of editorial argument, so an agent must work to extract the core purpose.

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?

Strong conditional guidance: it tells the agent what to report when the tool returns empty (a European cell → say the AGGREGATE holds no row; elsewhere → say no rate is published at any tier), and names find_opportunities.rate_band as the alternative instrument. It stops short of a crisp 'use this when X vs Y' rule and repeats the same distinction multiple times.

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 ~97 % of rows (re-measured 2026-08-14 on the published tranche: 96,8 %), meaning none declared here, never that the firm has no partners. 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.4/5.0
Behavior5/5

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

Beyond the annotations (readOnly, idempotent, destructiveHint false), the description adds critical behavioral context: it explains that `partnerships_declared` being 0 means none declared, not that the firm has no partners, and provides measured frequency data. It also notes that a refused slug does not mean the firm is absent, because the published tranche is an editorial subset. This is valuable nuance not captured 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.

Conciseness4/5

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

The description is efficiently structured: an action sentence, a return-semantics sentence, and an exclusions sentence. It is slightly dense due to the parenthetical measurement detail ('re-measured 2026-08-14... 96,8 %'), but every clause contributes to understanding. It is front-loaded and appropriately sized for a tool with this nuance.

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 and annotations, the description is complete. It explains the key return field's semantics and the potential misinterpretation, while the schema covers slug handling in depth. No important behavioral or usage aspects are left undocumented for the tool's complexity.

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?

With schema description coverage at 100%, the baseline is 3. The tool description merely recaps the slug source ('rows[].slug from search_firms') already fully detailed in the schema, adding no new parameter semantics. The schema itself contains extensive guidance on numeric slugs and refusal behavior, but that belongs to the schema, not the tool description.

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 action ('Fetch one organisation from the published directory') and the specific resource ('published profile'), using the verb 'Fetch' with a precise scope. It distinguishes from siblings by noting it returns public fields and does not return the paid firm-intelligence profile or contacts, making its 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 Guidelines4/5

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

The description implies when to use the tool (when a single firm's published profile is needed) and tells the user to source the slug from search_firms, noting that the slug is taken verbatim. It also states exclusions (no paid profile, contacts, or persons), which hints at alternatives, but it does not explicitly name tools like get_firm_intel or search_firms for those needs.

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 the entire corpus as of 2026-08-10, 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. 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.2/5.0
Behavior4/5

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

The readOnlyHint and idempotentHint annotations already establish safe behavior, and the description adds valuable behavioral detail: null fields mean 'not researched', absent filter results mean 'not declared', and person-shaped fields are never returned at any tier. There is no contradiction between the annotations and the description's behavioral claims.

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 description is excessively long and contains redundant, confusingly repeated content, especially around data completeness percentages and sparsity warnings. Statements like the repeated 'READ THE SPARSITY BEFORE QUOTING A ROW' block and the garbled 'it read ~39% ... below the truth' passage create noise rather than adding clear value. A tighter description would convey the same essential caveats more effectively.

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?

Given the tool's complexity, the description covers the essential operational context: access tier, sparse-data semantics, filter behavior, and data-population differences from sibling tools. It does not include the output schema details, but the description still gives enough context for an agent to understand what the endpoint will and will not return.

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?

Every parameter in the schema has a detailed, meaningful description that goes beyond basic type information: name matching is case-insensitive, cursor must be passed back with identical filters, limit has a hard cap of 10, and mode/delivers are explicitly declared-only. These explanations remove ambiguity and give an agent precise instructions for composing valid requests.

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 identifies the tool as a paid intelligence profile for services firms, listing the exact data categories it provides (SAP practice size, partner level, delivery flags, day rate, clients, analytics summary, LinkedIn URL). It also distinguishes this tool from the public directory by stating that search_firms covers a different, wider population, so an agent can confidently choose it for firm-intelligence lookups.

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 gives concrete usage guidance: requires a Legend-tier API key, search by name, and clarifies that omissions in filter results mean 'not declared' rather than 'cannot deliver'. It also warns against quoting sparse fields and states that person-shaped fields are never served, giving an agent clear guardrails for when to use and when not to use this endpoint.

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 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.
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

A4.9/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 behavior, and the description adds substantial context beyond them: the graph projection can change and is recomputed per call, `_meta.available_edge_types` is authoritative, missing node types are documented rather than silent, language fallback is declared, and cursors are refused when filters change. It even gives a historical example of why the dynamic edge list must be trusted.

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 content is front-loaded and nearly every sentence carries information, but the prose is dense and rhetorically heavy (all-caps emphasis, multiple parentheticals, repeated meta-field warnings). It is appropriately sized for the 8-parameter complexity, though a modest trim would improve readability without losing substance.

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 complex tool with 8 optional parameters and an output schema, the description covers scope, edge types, pagination cursor semantics, language fallback, node listing vs walking, and meta fields. There is no gap that would prevent an agent from selecting and invoking it correctly.

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?

Schema coverage is 100%, but the description adds meaning the schema alone lacks: node_id switches the response from nodes to that node's edges, direction is ignored without node_id, query targets the served label and follows lang, and edge_type is a hint rather than authoritative. Parameter descriptions also give realistic id examples and behavioral edge cases.

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: it traverses relations/edges among Academy teaching objects, distinguishes itself as the only edge-serving tool on the server, and names the four node types it carries. The contrast 'Ask it what connects to what, not what exists' cleanly separates it from row-serving siblings.

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 tells when to use it ('Ask it what connects to what, not what exists'), states it is the only edge tool while sibling tools serve rows, and documents the scope boundaries with excluded node types. It also gives concrete invocation patterns (node_id for a walk; node_type/query to find a node id first), so an agent need not infer when it applies.

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, 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: the whole matched set is reachable — pass _meta.next_cursor back as cursor with the same filters until it is null. 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.
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.4/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), yet the description adds substantial operationally relevant behavior beyond them: full pagination via _meta.next_cursor, cursor refusal when filters change, name-match ordering tiers when `query` is set, and quality ranking otherwise. The flag-vs-kind-code precedence rule for `is_client` is an unusual and genuinely important disclosure.

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 with purpose and scope, then pagination and ordering; every sentence carries information. The flag-versus-kind digression is the longest stretch and slightly repetitive ('the two are different columns', 'read the flag's meaning... not the label's'), which keeps it just short of fully 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?

An output schema exists, so return fields need not be spelled out, and the description still covers the essentials an agent needs: inclusion/exclusion boundary, pagination loop, ordering rules, and filter vocabulary sources. Nothing material is missing for a 6-param, optional-only search tool.

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 is already documented in the schema, including the cursor, module, kind and query semantics. The description largely restates those (module absence, cursor reuse) and only the query ordering tiers add meaning beyond the schema. Baseline 3 is appropriate when the schema does the heavy lifting.

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 ('Search the published Analytics Legends directory of SAP AI & analytics service providers') and enumerates the entity kinds it covers. It also carves out the boundary against the end-customer dataset, so an agent can distinguish it from find_sap_clients/get_sap_client_profile without opening another 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 clear usage context: what is included, what is excluded (SAP end-customers live in a separate paid dataset), and routes the agent to count_firms_by for live module vocabulary and list_firm_kinds for kinds. It stops short of naming the sibling tool that serves the excluded end-customers, leaving that inference to the agent.

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
      • 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."
  2. 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"
        +]
  3. 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"
        +  ]
        +}
  4. 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."
  5. 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."
  6. 1 tool update
    • Changedget_day_rate_benchmark26 fields changed
      • addedOutput schema / properties / rows / items / properties / basis
        Added value: +{
        +  "description": "external_published_median | external_published_range | panel — see /api/market-rates.json _meta.basis_values.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / confidence
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / currency / description
        Added value: +"ISO-4217 unit of every daily_rate_* amount on THIS row (EUR, GBP, CHF) — never an envelope constant."
      • addedOutput schema / properties / rows / items / properties / daily_rate_max
        Added value: +{
        +  "description": "Upper bound of the published band, in `currency`.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_max_chf
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_max + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_max_eur
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_max + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_max_gbp
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_max + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_median
        Added value: +{
        +  "description": "Median when the source publishes one, in `currency`.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_median_eur
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_median + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_median_gbp
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_median + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_min
        Added value: +{
        +  "description": "Lower bound of the published band, in `currency`.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_min_chf
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_min + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_min_eur
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_min + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_min_gbp
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_min + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_p10
        Added value: +{
        +  "description": "10th percentile when the source publishes one, in `currency`.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_p10_gbp
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_p10 + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_p90
        Added value: +{
        +  "description": "90th percentile when the source publishes one, in `currency`.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / daily_rate_p90_gbp
        Added value: +{
        +  "description": "DEPRECATED — read daily_rate_p90 + currency.",
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / note
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / sample_basis
        Added value: +{
        +  "description": "What `sample_size` counts (profiles, vacancies…), when the source says.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / sample_size / description
        Added value: +"The sample the SOURCE states behind this row; null when it states none — never estimated."
      • addedOutput schema / properties / rows / items / properties / source
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / source_date
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / source_url
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / trend
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / yoy_change_pct
        Added value: +{
        +  "type": [
        +    "number",
        +    "null"
        +  ]
        +}
  7. 1 tool update
    • Changedlist_studies3 fields changed
      • changedInput schema / properties / lang / description
        Previous value: -"Language code, EN or FR."New value: +"ISO-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."
      • addedOutput schema / properties / rows / items / properties / editions
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": [
        +    "array",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / lang
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
  8. 1 tool update
    • Changedfind_opportunities3 fields changed
      • addedInput schema / properties / lang
        Added value: +{
        +  "description": "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`.",
        +  "pattern": "^[A-Za-z]{2}$",
        +  "type": "string"
        +}
      • addedOutput schema / properties / rows / items / properties / source_lang
        Added value: +{
        +  "description": "ISO-639-1 language the ADVERTISER wrote the title in, measured by the harvest chain and never changed by `lang` — 'en' on 1,566 rows, 'de' on 337 (2026-08-23). Null when the chain could not decide, and on the promoted leg. Compare it with `title_lang` to know whether the title you are quoting is the advertiser's own words or this platform's rendering of them: a harvested posting is a third party's text, and saying which of the two you are citing is the only honest way to quote it.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • changedOutput schema / properties / rows / items / properties / title_lang / description
        Previous value: -"ISO-639-1 language the TITLE is written in, as measured by the harvest chain — 'en' on 1,566 rows, 'de' on 337 (2026-08-23). Null when the chain could not decide. A harvested title is a third party's own words and is never translated, so this label is the only honest way to tell a reader the posting you are quoting is not in their language. Absent from the served payload until 2026-08-23: the projector's keep-list dropped it."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 (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."
  9. 1 tool update
    • Changedquery_knowledge_graph4 fields changed
      • addedInput schema / properties / lang
        Added value: +{
        +  "description": "ISO-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.",
        +  "pattern": "^[A-Za-z]{2}$",
        +  "type": "string"
        +}
      • changedInput schema / properties / query / description
        Previous value: -"Case-insensitive substring of a node's label. Applies to the NODE listing, not to a walk."New value: +"Case-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`."
      • addedOutput schema / properties / rows / items / properties / label / description
        Added value: +"In the language asked for when the node carries it, else the declared fallback — see `label_lang`."
      • addedOutput schema / properties / rows / items / properties / label_lang
        Added value: +{
        +  "description": "The language the served `label` is actually written in. It differs from the requested `lang` exactly when that translation does not exist.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
  10. 1 tool update
    • Changedfind_opportunities1 field changed
      • changedOutput schema / properties / rows / items / properties / rate_band / description
        Previous value: -"Editorial benchmark for this posting's (seniority × product × region) cell — NOT a rate the employer offered. `basis` and `kind` are inside the object on purpose, so no extraction can lift the numbers away from what they mean."New value: +"Editorial benchmark for this posting's (seniority × product × region) cell — NOT a rate the employer offered. `basis` and `kind` are inside the object on purpose, so no extraction can lift the numbers away from what they mean. 2026-08-29: this band is a SUBSCRIBER surface and is no longer carried by the public radar file this lane reads — expect it to be null here. The posting's OWN published rate, when it has one, stays in currency / daily_rate_min / daily_rate_max."
  11. 1 tool update
    • Changedfind_opportunities2 fields changed
      • changedInput schema / properties / country / description
        Previous value: -"ISO-3166-1 alpha-2 code, applied to both legs as a predicate on the row's own country_code. It effectively selects the SITE-RADAR leg: the promoted feed leaves country_code NULL on all but a handful of its active rows, so a country filter drops the rest of that leg because they do not match, not because the 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."New value: +"ISO-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."
      • 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 stores contract_type NULL on every one of its active rows, so any value here drops that leg by predicate — `_meta.note` says so. 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 population and either alone is not. Read both before quoting a mix."
  12. 3 tool updates
    • Changedfind_academy_modules1 field changed
      • changedOutput schema / properties / rows / items / properties / status / description
        Previous value: -"Editorial access marker: 'available' or 'legend-pass'. Served, not filtered on."New value: +"Editorial access marker: 'available' or 'legend-pass'. Served, not filtered on. READ 'legend-pass' AS 'sold at the CONSULTANT tier': it is a LEGACY label from when the paid tier was called Legend, it sits on the majority of rows, and the tier that actually opens the body here is Consultant (get_academy_module). The site stopped rendering this field for exactly that reason on 2026-07-26; this endpoint still serves the raw value, so the LABEL is the stale half and the tier floor is the true one."
    • 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 stores contract_type NULL on every one of its active rows, so any value here drops that leg by predicate — `_meta.note` says so."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 stores contract_type NULL on every one of its active rows, so any value here drops that leg by predicate — `_meta.note` says so. 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."
    • Changedquery_knowledge_graph1 field changed
      • changedInput schema / properties / edge_type / description
        Previous value: -"Restrict a walk to one relation (teaches · taught_by · covers · related). Read `_meta.available_edge_types` — it is computed, never written here."New 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."
  13. 9 tool updates
    • Changedcount_firms_by1 field changed
      • changedInput schema / properties / query / description
        Previous value: -"Free-text filter, matched case-insensitively."New value: +"Free-text filter, case-insensitive. EVERY word must appear in the record (substring per word, any order), so a natural-language phrase narrows the answer instead of having to match verbatim."
    • Changedfind_academy_modules1 field changed
      • changedInput schema / properties / query / description
        Previous value: -"Free-text filter, matched case-insensitively."New value: +"Free-text filter, case-insensitive. EVERY word must appear in the record (substring per word, any order), so a natural-language phrase narrows the answer instead of having to match verbatim."
    • Changedfind_opportunities1 field changed
      • changedInput schema / properties / query / description
        Previous value: -"Free-text filter, matched case-insensitively."New value: +"Free-text filter, case-insensitive. EVERY word must appear in the record (substring per word, any order), so a natural-language phrase narrows the answer instead of having to match verbatim."
    • Changedfind_sap_clients1 field changed
      • changedInput schema / properties / query / description
        Previous value: -"Free-text filter, matched case-insensitively."New value: +"Free-text filter, case-insensitive. EVERY word must appear in the record (substring per word, any order), so a natural-language phrase narrows the answer instead of having to match verbatim."
    • Changedlist_sap_modules1 field changed
      • changedInput schema / properties / query / description
        Previous value: -"Free-text filter, matched case-insensitively."New value: +"Free-text filter, case-insensitive. EVERY word must appear in the record (substring per word, any order), so a natural-language phrase narrows the answer instead of having to match verbatim."
    • Changedlist_studies1 field changed
      • changedInput schema / properties / query / description
        Previous value: -"Free-text filter, matched case-insensitively."New value: +"Free-text filter, case-insensitive. EVERY word must appear in the record (substring per word, any order), so a natural-language phrase narrows the answer instead of having to match verbatim."
    • Changedsearch_concepts1 field changed
      • changedInput schema / properties / query / description
        Previous value: -"Free-text filter, matched case-insensitively."New value: +"Free-text filter, case-insensitive. EVERY word must appear in the record (substring per word, any order), so a natural-language phrase narrows the answer instead of having to match verbatim."
    • Changedsearch_firms1 field changed
      • changedInput schema / properties / query / description
        Previous value: -"Free-text filter, matched case-insensitively."New value: +"Free-text filter, case-insensitive. EVERY word must appear in the record (substring per word, any order), so a natural-language phrase narrows the answer instead of having to match verbatim."
    • Changedsearch_news1 field changed
      • changedInput schema / properties / query / description
        Previous value: -"Free-text filter, matched case-insensitively."New value: +"Free-text filter, case-insensitive. EVERY word must appear in the record (substring per word, any order), so a natural-language phrase narrows the answer instead of having to match verbatim."
  14. 1 tool update
    • Changedfind_opportunities1 field changed
      • addedOutput schema / properties / rows / items / properties / title_lang
        Added value: +{
        +  "description": "ISO-639-1 language the TITLE is written in, as measured by the harvest chain — 'en' on 1,566 rows, 'de' on 337 (2026-08-23). Null when the chain could not decide. A harvested title is a third party's own words and is never translated, so this label is the only honest way to tell a reader the posting you are quoting is not in their language. Absent from the served payload until 2026-08-23: the projector's keep-list dropped it.",
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
  15. 11 tool updates
    • Changedfind_academy_modules1 field changed
      • changedInput schema / properties / cursor / description
        Previous value: -"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place."New value: +"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor."
    • Changedfind_opportunities1 field changed
      • changedInput schema / properties / cursor / description
        Previous value: -"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place."New value: +"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor."
    • Changedfind_sap_clients1 field changed
      • changedInput schema / properties / cursor / description
        Previous value: -"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place."New value: +"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor."
    • Changedget_firm_intel1 field changed
      • changedInput schema / properties / cursor / description
        Previous value: -"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place."New value: +"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor."
    • Changedlist_freelance_platforms1 field changed
      • changedInput schema / properties / cursor / description
        Previous value: -"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place."New value: +"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor."
    • Changedlist_sap_modules1 field changed
      • changedInput schema / properties / cursor / description
        Previous value: -"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place."New value: +"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor."
    • Changedlist_studies1 field changed
      • changedInput schema / properties / cursor / description
        Previous value: -"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place."New value: +"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor."
    • Changedquery_knowledge_graph1 field changed
      • changedInput schema / properties / cursor / description
        Previous value: -"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place."New value: +"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor."
    • Changedsearch_concepts1 field changed
      • changedInput schema / properties / cursor / description
        Previous value: -"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place."New value: +"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor."
    • Changedsearch_firms1 field changed
      • changedInput schema / properties / cursor / description
        Previous value: -"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place."New value: +"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor."
    • Changedsearch_news2 fields changed
      • changedInput schema / properties / category / description
        Previous value: -"Category 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 (Robinhood, Stripe, Google Pay); the collision pressure is structural and the bucket has kept growing since. For genuine SAC product news, pair category:'SAC' with query:'analytics cloud'."New value: +"Category 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'."
      • changedInput schema / properties / cursor / description
        Previous value: -"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments to read the next page; a null `next_cursor` means you have reached the end. It is bound to those filters and refused if they change — a cursor names a POSITION in one ordering, and applying it to another query would start the page in the wrong place."New value: +"Opaque token from a previous response's `_meta.next_cursor`. Pass it back with the SAME filter arguments; `null` means the last page. Changing a filter refuses the cursor."
  16. 3 tool updates
    • Changedfind_academy_modules6 fields changed
      • addedOutput schema / properties / rows / items / properties / summary_fr
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / tags
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": [
        +    "array",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / title_fr
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / track_name_en
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / track_name_fr
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / updated_at
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
    • Changedsearch_firms8 fields changed
      • addedOutput schema / properties / rows / items / properties / claim
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / founded_year
        Added value: +{
        +  "type": [
        +    "integer",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / hq_city
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / jobs_url
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / last_verified_at
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / region
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / sap_signal_band
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
      • addedOutput schema / properties / rows / items / properties / size_bracket
        Added value: +{
        +  "type": [
        +    "string",
        +    "null"
        +  ]
        +}
    • Changedsearch_news2 fields changed
      • addedInput schema / properties / published_since
        Added value: +{
        +  "description": "Lower 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.",
        +  "type": "string"
        +}
      • addedInput schema / properties / published_until
        Added value: +{
        +  "description": "Upper 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.",
        +  "type": "string"
        +}
  17. 1 tool update
    • Changedcount_firms_by1 field changed
      • changedInput schema / properties / by / enum
        Previous value: -[
        -  "country",
        -  "kind",
        -  "sap_signal_band"
        -]New value: +[
        +  "country",
        +  "kind",
        +  "module",
        +  "sap_signal_band"
        +]

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    B
    maintenance
    ABAPilot by Crimson Consulting connects AI coding assistants to SAP ECC and on-premise S/4HANA through MCP. Use SAP source and dictionary context for ABAP explanation, review and development. Available operations depend on the deployed version and enabled endpoints. The external connector is MIT-licensed; a licensed SAP-side ABAPilot backend is required.
    49
    3
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables SAP Value Advisors to instantly generate prioritized recommendations for client pain points by leveraging historical cases, Next-Gen Ariba features, and VLM KPIs, producing structured analysis cards with effort, timeline, and benefits.
    -
Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.