INFABODE
ANYWHERE
Overview
Our Data
AI Connectors
Pricing
Sign In
Get Started
Contents
1) Purpose
2) Global Rules Claude Must Follow
3) Component Catalog
3.1 System tools
3.2 Domain tools
3.3 Schema tools
3.4 Prompt
4) Resources Catalog
4.1 System resources
4.2 Domain resources
4.3 Schema resources
5) Filter Reference
5.1 `orgs.list` -> `filter`
5.2 `posts.search` -> `filter`
5.3 `posts.search` -> `filter_opts`
5.4 `schema.locations.list` -> `filter`
6) Recommended Calling Pattern
7) Error Handling Guidance
8) Canonical Examples
9) Anti-Patterns
Infabode MCP Documentation
Practical contract for AI assistants (Claude, ChatGPT, Cursor, and other LLM clients) calling the infabode-mcp server.
For AI Assistants
This page is published as plain Markdown for machine consumption. Point your model or agent at the raw URL below for the most reliable parsing.
View raw Markdown
Download (.md)
https://ia.infabode.com/mcp-guide.md
1) Purpose
This guide is the practical contract for Claude when calling infabode-mcp. It covers:
registered tools/resources/prompts;
argument and response shapes;
validated filters for list/search tools;
behavior under ACL projection and method restrictions;
common failure modes and safe calling patterns.
Scope: MCP read-only rollout (tools/resources/prompts in Mcp.Server)
1.1 Using this guide with Claude
Claude Desktop: keep this file in project instructions or link it from the MCP server entry in the Desktop config so the model sees transport-local rules (especially filters and pagination).
Claude.ai (Projects): add to project knowledge or custom instructions; the chat runtime performs MCP discovery—you use this document for argument shapes and validation rules.
Claude Code: add this guide to project or team rules so agents share one contract for infabode-mcp.
1.2 Operational checklist (Claude)
Use a real access token on every request; never paste or repeat bearer tokens in user-visible replies.
After a new connector session or transport change, run tools/list again before assuming tool names or JSON schemas.
Pass filter and filter_opts as objects, never as stringified JSON.
For posts.search, integer ID list fields may use JSON integers and/or digit-only strings per element (e.g. "content_types_ids": [1, "2"]); the server coerces to integers. Each field is still an array (or null), never a single scalar ID. Do not use JSON floats for ID elements.
Post geo: coordinates as [[lon, lat], ...]; radius in meters; orders closer, tree_up, tree_down, and distance only make sense with locations_ids and/or coordinates in filter (see §3.2).
For rank order, include a non-empty filter.search.
Keep the same order and filter (and filter_opts) while following page.next_offset.
Before large or novel filter payloads, call prompt schema.explain.filters with entity: "posts" or "orgs".
For “what is happening around this address/building”, call asset.context.get (not a manual posts.search geo stack); then asset.context.analyze for the narrative.
asset.context.get: sector_ids, content_type_ids, and include use the same integer / digit-string list coercion as posts.search filter ID lists. Response is structuredContent (not a text JSON blob); empty lists/maps are omitted after compaction.
2) Global Rules Claude Must Follow
Bearer auth: the MCP host (Desktop config, OAuth flow, or deployment) must attach Authorization: Bearer <token>; the model must not invent headers. Do not expose tokens in assistant text.
Start sessions with discovery:
tools/list
resources/list
optional: prompts/list
tools/list/resources/list visibility does not guarantee execution permission.
ACL can hide fields (field_access) even when calls succeed.
Pagination is offset-based:
input: limit, offset
output: page.limit, page.offset, page.next_offset, page.has_more
filter/filter_opts must be JSON objects (not stringified JSON).
Geo coordinates for post search must be an array of [lon, lat] pairs: [[lon, lat], ...]. radius is an integer in meters.
Geo points in responses are JSON objects:
{ "type": "Point", "coordinates": [lon, lat], "srid": 4326, "properties": {} }
IDs: In posts.search / filter_opts, list ID elements = JSON int or digit-only string (no floats). Top-level post_id / org_id / id = same for scalars.
3) Component Catalog
3.1 System tools
system.status.get
Permission: mcp.system.status.read
Input: none
Output:
status ("ok")
env
mcp_app_version
infabode_backend_version
timestamp (ISO8601 UTC)
viewer.get
Permission: mcp.viewer.get
Input: none
Output (subject to projection):
id, email, first_name, last_name, role, company, job_title
3.2 Domain tools
For tools below that take a single path-style id argument: JSON integer or digit-only string (entire string must be the integer; no floats). Where the bullet says “integer”, that is the logical type after parsing; hosts may send a string.
orgs.list
Permission: mcp.orgs.read
Input:
limit integer, default 20, max 100
offset integer, default 0
order: newer | older | name_asc | name_desc | relevance | by_index | by_suggested (default name_asc)
filter object (section 5.1)
Output:
items: org[]
page
Org item shape before projection:
id, name, phone, email, domains, location
orgs.get
Permission: mcp.orgs.read
Input: org_id integer (required)
Output: one org object (not wrapped)
Important: token org scope is enforced (Forbidden org scope).
posts.search
Permission: mcp.posts.read
Input:
limit integer, default 10, max 30
offset integer, default 0
order string, default newer published_at DESC. Allowed values: newer, older, closer, rank, tree_up, tree_down, distance, location_type.
newer, older: general listing/sort.
closer, tree_up, tree_down, distance, location_type: use only with geo context—supply filter.locations_ids and/or filter.coordinates (and radius when relevant).
location_type: accepted for compatibility; prefer tree_up or tree_down for hierarchical location ordering (§5.2.1).
rank: only use with full-text filter.search.
The tool’s published JSON schema may list a subset; trust tools/list at runtime and this guide for the full set.
filter object (section 5.2)
filter_opts object (section 5.3)
ID list coercion (JSON integer and/or digit-only string per element) applies only inside filter and filter_opts (§5.2–5.3). Top-level post_id / org_id / id are separate scalars (§3.2 header): same integer / digit-string rules as §2.
Output:
items: post[]
page
Post list item shape before projection (see §3.2.1):
id, org, content_type, locations
is_external, is_starred
published_at, built_at, title, summary, ai_summary
url, tags, address_label, sentiment_score
price, currency
size_range_l, size_range_h, size_total, land_size, lease_length
occupancy_rate, vacancy_rate
cap_rate, noi, pps
listing_status, job_status
sellers, selling_agents, buyers, buying_agents
search_rank, search_snippet
distance_city, distance_point
location_type, location_id, location_path
posts.get
Permission: mcp.posts.read
Input: post_id integer (required)
Output: one post object (not wrapped)
Post get item shape before projection (same property fields as list §3.2.1):
id, org, content_type, locations
is_external, is_starred
published_at, built_at, title, summary, ai_summary
url, tags, address_label, sentiment_score
price, currency
size_range_l, size_range_h, size_total, land_size, lease_length
occupancy_rate, vacancy_rate
cap_rate, noi, pps
listing_status, job_status
sellers, selling_agents, buyers, buying_agents
posts.get does not include search/geo list-only fields:
search_rank, search_snippet
distance_city, distance_point
location_type, location_id, location_path
asset.context.get
Permission: mcp.asset_context.read
Purpose: structured market activity around a specific asset address (posts, transactions, organizations in radius + date window). Prefer this over hand-built posts.search when the user asks what is happening around a building or address.
Response transport: structuredContent object.
Input:
address string (required) — free-form address; server geocodes via Google Maps.
date_from, date_to ISO date strings YYYY-MM-DD (optional). Default: date_to = today (UTC calendar), date_from = 14 days earlier. date_from must be on or before date_to.
radius_meters integer (optional). Default: 3000. Geo uses fallback: :off (strict radius, no 300 km expand).
sector_ids integer ID list (optional; JSON int or digit-only string per element). Industry sectors (ind_sectors_ids / transaction ind_sector_ids). Default: all sectors.
content_type_ids integer ID list (optional; same coercion). Intersected with allowed_content_ids for the API user. Default: all allowed types.
include string list (optional, case-insensitive). Narrows which domains are searched and returned: posts, transactions, organisations. Default: all three. Unknown values are rejected; empty list is rejected.
limit integer (optional). Default: 20, max 50. Caps post items per content-type bucket and transaction groups only (not top_organisations, fixed at 10).
Output (sections depend on include; always present: resolved_asset, query_context):
resolved_asset: address, lat, lng, matched_location_id, matched_location_name (location path)
query_context: echoed filters including include, date_from, date_to, radius_meters, sector_ids, content_type_ids
activity: counts only for included domains — posts_count, transactions_count, organisations_count
posts_by_content_type (if posts ∈ include): map keyed by content-type slug (e.g. market_news; trailing _item stripped from type name before slugify) → {content_type_id, count, items[]}; post items use same shape as posts.search list items (subject to ACL)
top_organisations (if organisations ∈ include): up to 10 rows [{organisation_id, name, mentions_count}, ...] ranked by mentions in the geo/date window (post source org, party fields, content_data party org ids, transaction org roles). Requires field_access.org allow — if org struct is denied, counts stay 0 and this section is omitted; do not treat as “no organizations in market”.
transactions (if transactions ∈ include): transaction groups — [{transaction_group_id, started_at, data_extraction_transactions: [...]}]; each transaction may include convenience buyer / seller from transaction_orgs (org names subject to ACL) plus fields such as price, currency, transaction_date, address_label when present (do not invent missing fields)
ACL: posts ∈ include requires field_access.post allow; transactions ∈ include requires data_extraction_transaction allow.
Workflow: call asset.context.get first; for narrative answers use prompt asset.context.analyze (§3.4).
Errors: geocode failure → Address could not be resolved; invalid include / date order / denied struct → execution error with message.
locations.get
Permission: mcp.locations.read
Input: id integer (required)
Output before projection:
id, type, name, code, path
point, city_point
continent_id, country_id, sub1_id, sub2_id, city_id, sub3_id, sub4_id, sub5_id
locations.by_post
Permission: mcp.locations.read
Input: post_id integer (required)
Output:
{ "items": [location, ...] }
3.3 Schema tools
Paired list resources (§4.3): root is bare [...]; list tools wrap {"items": [...]}.
schema.content_types.list
Permission: mcp.content_types.read
Input: none
Output: { "items": [{"id","name"}, ...] }
schema.industry_sectors.list
Permission: mcp.industries.read
Input: none
Output: { "items": [{"id","name","parent_id"}, ...] }
schema.job_sectors.list
Permission: mcp.job_sectors.read
Input: none
Output: { "items": [{"id","name","parent_id"}, ...] }
schema.locations.list
Permission: mcp.locations.read
Input:
limit integer, default 100, max 500
offset integer, default 0
filter object (section 5.4)
Output:
items (location[])
page
schema.industry_sectors.by_post
Permission: mcp.industries.read
Input: post_id integer (required)
Output: { "items": [{"id","name","parent_id"}, ...] }
schema.job_sectors.by_post
Permission: mcp.job_sectors.read
Input: post_id integer (required)
Output: { "items": [{"id","name","parent_id"}, ...] }
schema.ai_summary.by_post
Permission: mcp.posts.read
Input: post_id integer (required)
Output:
post_id
data
3.4 Prompt
schema.explain.filters
Purpose: guidance prompt for valid filters/order for posts.search and orgs.list
Input:
entity: "posts" or "orgs"
Output: prompt messages (instructional text), not business data
asset.context.analyze
Purpose: how to present results from asset.context.get to the user (summarize, prioritize recent/geo-close items, notable transactions, recurring organizations, dominant content types; avoid raw dumps; do not invent transaction fields; state when data is limited)
Input: optional address string (labels the analysis prompt; does not fetch data)
Output: prompt messages (role: user only), not business data
When to use: after a successful asset.context.get call when the user wants an interpreted market-activity answer around an address
4) Resources Catalog
Many URIs mirror tools; body shape can differ (§4.3).
4.1 System resources
resource://system/status -> equivalent to system.status.get
infabode://viewer -> equivalent to viewer.get
4.2 Domain resources
resource://orgs/{org_id} -> equivalent to orgs.get
resource://posts/{post_id} -> equivalent to posts.get
resource://locations/{id} -> equivalent to locations.get
resource://locations/{post_id} -> equivalent to locations.by_post
Note: resource://locations/{id} and resource://locations/{post_id} share the same URI pattern. Prefer tools for deterministic intent if ambiguity appears in a client.
4.3 Schema resources
Bare array [...] (same rows as tool items, no wrapper): resource://schema/content-types, …/industry-sectors, …/job-sectors.
items + page: resource://schema/locations — like schema.locations.list; limit/offset query params only; query filter is not applied on this resource URI.
Object like tool: resource://schema/industry-sectors/{post_id}, …/job-sectors/{post_id} → {"items": [...]}.
resource://schema/ai_summary{post_id} — {post_id, data}; brace is template (substitute id, not /post_id/).
5) Filter Reference
5.1 orgs.list -> filter
Supported keys:
name (string)
email (string)
ib_org_types__any (string[]/atom[])
ib_org_types__all (string[]/atom[])
Validation behavior:
unknown keys -> Unknown filter key: <key>
wrong shape -> Invalid filter value for <key>
unknown org type -> Unknown ib_org_type: <value>
5.2 posts.search -> filter
Canonical rule:
filter (object) holds post predicates; keys are listed below. filter_opts (object, optional) holds auxiliary options—see §5.3.
Unknown keys inside filter or filter_opts (including nested org_activity_by) are rejected; unsupported order values are rejected.
Allowed keys:
IDs/relations: ids, exclude_ids, sources_ids, not_sources_ids, ind_sectors_ids, job_sectors_ids, locations_ids, mentions_source_ids, not_mentions_source_ids, sellers_ids, selling_agents_ids, buyers_ids, buying_agents_ids, content_types_ids, mentions_ids
text: title, search, address_label
tags: tags, and_tags, or_tags, not_tags
status flags: is_paid, is_public, is_external, is_subscription_required, is_starred
geo: coordinates, radius, fallback — §5.2.2
Location hierarchy (only when filter.locations_ids is non-empty): optional zoom — see subsection §5.2.1
recruiting: job_types, working_location_types, job_recruiters, listing_status, job_status
ranges: property_size, size_total, size_total_or_property_size, salary, price, pps, cap_rate, noi, land_size
date/time: built_at, published_at (each {min,max} ISO8601 date or datetime string)
other: currency, sentiment_score
Shape rules:
list fields (generic): null or array
Integer ID list fields in filter (ids, exclude_ids, sources_ids, not_sources_ids, ind_sectors_ids, job_sectors_ids, locations_ids, mentions_source_ids, not_mentions_source_ids, sellers_ids, selling_agents_ids, buyers_ids, buying_agents_ids, content_types_ids, mentions_ids): null or array; each element is coerced to an integer if it is:
a JSON integer, or
a string: leading/trailing whitespace stripped, then the entire string must parse as an integer with no leftover characters (so "123" is valid; "123.0" is not).
Do not send ID elements as JSON floats; use integers or digit-only strings.
Not integer ID lists: tags, and_tags, or_tags, not_tags, job_types, working_location_types, job_recruiters, listing_status, job_status. For job sectors by id prefer job_sectors_ids (over job_sectors).
boolean fields: null or boolean
text fields: null or string
radius: null or integer
coordinates: omit when not using point-based geo. If the key is present, use a non-empty [[lon, lat], ...] (JSON numbers per coordinate). null and [] invalidate the filter (Invalid filter value for coordinates). You may still get Invalid coordinates if the list pipeline rejects the geo payload after validation.
range maps: only supported keys
generic ranges: min, max — JSON number or numeric string per bound; server coerces to float
price: min, max, currency
size_total_or_property_size: property_size, size_total
property_size filters content_data.size_range_l / size_range_h (not separate filter keys)
enum fields (rejected if unknown): zoom, fallback, currency, sentiment_score, job_types, working_location_types, job_recruiters, listing_status, job_status; filter_opts.mentions_include; filter_opts.org_activity_by.mentions. Case/underscore variants like "full_time" / "FULL_TIME" are normalized when recognized.
5.2.1 zoom (with filter.locations_ids)
Only when locations_ids is non-empty; otherwise zoom is ignored. Type: null or "in" / "out" / "off" (default in if omitted).
off: strict — location_id must be in the given id list.
out: ids expanded along hierarchy; match against expanded set.
in: match via denormalized hierarchy columns on post_locations (not the same as off).
order when location filter active: off → newer/older (default newer); out → +tree_up (default tree_up); in → +tree_down (default newer).
With non-empty locations_ids: geo map is empty — coordinates / radius / fallback ignored; do not mix geo-centric order (closer, …) with this path (§3.2).
5.2.2 fallback (with coordinates + radius)
Use with filter.coordinates and filter.radius (meters). Meaning matches for geo posts filters:
off (FALLBACK = OFF): returns content only from within your radius.
tree (FALLBACK = TREE): returns content within the radius, then content linked to the nearest city/state, country and continent (radius match or hierarchy ids.
expand (FALLBACK = EXPAND, default): widens discovery so you can reach up to 100 useful rows from nearby locations—i.e. not limited to the strict small disk only; backend uses the expand path and a large effective search radius.
Omitted fallback → expand; bad/missing radius → 5000 m. Type: null or "off" / "tree" / "expand". Ignored when locations_ids non-empty (§5.2.1).
How it affects order: When geo is active on non-empty geo_filter), allowed order / default if yours is invalid depend on fallback: off → newer, older, or closer (default closer); tree → newer, older, or tree_up (default tree_up); expand → newer, older, or closer (default newer). For tree, prefer tree_up over tree_down here—see §3.2 for when geo-centric orders are valid.
When ignored: Same rule as §5.2.1: if filter.locations_ids is non-empty, the geo block is not built and fallback has no effect on the query.
5.3 posts.search -> filter_opts
Allowed keys:
mentions_include (list or null)
org_activity_by (object or null) — see §5.2.3.
mentions (list or null)
content_types_ids (list or null): each element uses the same integer ID coercion rules as filter ID lists (§5.2).
Validation behavior:
unknown keys in filter_opts -> Unknown filter_opts key: <key>
unknown keys in org_activity_by -> Unknown filter_opts.org_activity_by key: <key>
5.4 schema.locations.list -> filter
Allowed keys:
name (string)
path (string)
types (string[]/atom[])
Allowed types values:
global, continent, country, sub1, sub2, city, sub3, sub4, sub5, point
Validation behavior:
unknown keys -> Unknown filter key: <key>
invalid shape -> Invalid filter value for <key>
unknown type -> Unknown location type: <value>
6) Recommended Calling Pattern
Start with:
viewer.get
optional system.status.get if diagnosing environment issues
For unfamiliar filters or sort modes, call schema.explain.filters first, then build posts.search / orgs.list arguments.
Resolve schema list tools only when you need IDs for filters (content types, sectors, locations). Avoid prefetching every schema on every task.
Query domain data with small pages first (limit 10–30), then follow next_offset.
Keep order, filter, and filter_opts fixed across pages.
Use *.get tools after search/list results to fetch specific records.
7) Error Handling Guidance
Invalid params: check types and required IDs.
Invalid filter value for coordinates: filter.coordinates was null, [], malformed pairs, or wrong types—omit the key when unused, or send at least one [lon, lat] with JSON numbers.
Invalid coordinates: geo payload failed later in the list pipeline (after filter validation)—fix pair shapes/types so each coordinate is usable as a float pair.
Unknown ... key: delete the key; do not rename keys or guess alternate spellings (errors are exact-match on canonical names).
Invalid ... value for <key>: fix value shape/type.
Invalid filter.<key>: expected integer id, got ... or Invalid filter_opts.org_activity_by.content_types_ids: ...: fix each list element—use JSON integers or digit-only strings; not objects, booleans, floats, or strings like "42.0".
Invalid filter.<enum_field>... / filter_opts...: use allowed enum values only (§5.2); do not pass free-text status/zoom/fallback strings.
Invalid filter.<range>.min|max: expected number: bounds must be numeric (or numeric strings that parse fully).
Invalid filter.<date_range>.min|max: expected ISO8601 date or datetime: fix built_at / published_at bounds.
Method is not allowed: treat as permission limitation.
Struct fields are not allowed for '<struct>': treat as ACL projection restriction.
Forbidden org scope: do not retry with random org_id; token is org-scoped.
8) Canonical Examples
8.1 Geo post search
{
"order": "closer",
"limit": 10,
"filter": {
"coordinates": [[-0.1188, 51.5347]],
"radius": 5000
}
}
8.1a Resolve country location id (schema.locations.list)
Use this (not posts.search) to find a location id before passing it in posts.search → filter.locations_ids. filter.types is a string list; allowed values are in §5.4.
{
"limit": 1,
"offset": 0,
"filter": {
"types": ["country"],
"name": "United States"
}
}
Response shape: { "items": [{ "id", "type", "name", "path", ... }, ...], "page": { ... } }. Choose the row whose name / path matches the geography you need, then use that id in filter.locations_ids (see §8.1b).
8.1b Post search by location (United States country)
{
"order": "newer",
"limit": 10,
"filter": {
"locations_ids": [242]
}
}
242 is the United States country id in common Infabode seed/fixture data; production IDs differ—resolve with schema.locations.list (§8.1a: filter.types + filter.name / filter.path) or §5.4. For geo-centric sort modes (closer, tree_up, …) see §3.2 / §8.1.
8.1c Content type by name, then posts near a point (coordinates + radius)
Do not combine non-empty locations_ids (zoom) with coordinates/fallback in one call — split or pick one mode (§5.2.1–5.2.2).
1) schema.content_types.list {} → pick id from items by name (no server-side name filter).
Illustrative response fragment:
{ "items": [{ "id": 1, "name": "Market news" }], "page": { ... } }
Suppose the row you need has "id": 1.
2a) Posts around a strict point: content types + coordinates + closer + radius + fallback (no zoom here):
{
"order": "closer",
"limit": 10,
"filter": {
"content_types_ids": [1],
"coordinates": [[11.5812582, 48.1298707]],
"radius": 1000,
"fallback": "tree"
}
}
Use "fallback": "off" for strict, "tree" to include hierarchy around the nearest place (§5.2.2 / developer FALLBACK = TREE), or "expand" for the default widened search (FALLBACK = EXPAND).
2b) Same content types + hierarchy expansion (zoom: "out") — use locations_ids from schema.locations.list (§8.1a) and omit coordinates / radius / fallback; zoom only affects this path:
{
"order": "tree_up",
"limit": 10,
"filter": {
"content_types_ids": [1],
"locations_ids": [242],
"zoom": "out"
}
}
Replace [1] and [242] with ids from your environment.
8.2 Full-text post search + date range
{
"order": "rank",
"limit": 20,
"offset": 0,
"filter": {
"search": "industrial logistics",
"published_at": {
"min": "2026-01-01",
"max": "2026-12-31"
},
"built_at": {
"min": "2010-01-01",
"max": "2020-12-31"
},
"land_size": { "min": "1000", "max": 50000 },
"content_types_ids": [1, "2"]
}
}
Mixing JSON integers and digit-only strings in ID lists is intentional (hosts/LLMs often emit strings). Invalid examples: "content_types_ids": "1" (scalar instead of array), "2.0" as a string element, or JSON floats as elements.
8.3 Organization search
{
"limit": 10,
"order": "name_asc",
"filter": {
"name": "NAI"
}
}
8.4 AI summary by post
{
"post_id": 327596
}
9) Anti-Patterns
filter as stringified JSON.
Geo coordinates as {lat,lng} instead of [lon,lat].
Changing order, filter, or filter_opts while paginating.
Assuming all fields always appear (ACL projection exists).
Sending unregistered filter keys.
Pasting or repeating access tokens or raw Authorization headers in user-visible chat output.
Passing a single scalar where an ID list is required (e.g. "content_types_ids": 1 instead of [1]).
JSON floats or stringified decimals as ID elements (e.g. 123.0, "123.0")—use 123 or "123".
Assuming every list-valued filter field accepts stringified IDs; only the enumerated integer ID list keys in §5.2 (plus filter_opts.org_activity_by.content_types_ids) get coercion.
Insights
Privacy & Terms
Connect
Real Estate MCP
API Docs
Data Lake
Integrations
Advertizing
Use Cases
Contribute
Recruitment
Events