Skip to main content
Glama
Qobrix CRM MCP Server

Qobrix CRM MCP Server

by gca-ltd

Table of contents


What It Does

An AI assistant connected to this server can browse properties, qualify leads, track showings, review offers and contracts, audit follow-up activity, and discover CRM field schemas — all through natural language. Every tool description teaches the LLM which canonical real-estate workflow it belongs to, which RESO resource it maps to, and which tools to chain next.

Who it is for

  • Brokerages & developers using Qobrix who want Claude.ai, Dust.tt, ChatGPT, or Cursor to answer questions grounded in live CRM data (not copy-pasted exports).

  • Engineers wiring MCP into internal tools: stdio transport, typed Zod inputs, and no write surface — safe to experiment with prompts and agents.

  • Data & operations teams running dashboards: use qobrix_count / qobrix_top_values for YoY-style metrics without custom scripts, and response caching to cut API load on repeat queries.

  • Enterprise IT ready for per-agent identity: run none or api_key from this package, then pair the signed-header path with SharpSir’s Enterprise OAuth (SSO) product when every user must authenticate as themselves — see Enterprise OAuth.

Canonical RE Workflows

The server is organized around six RESO-aligned business processes. The LLM receives these as built-in instructions so it can navigate the CRM without prior training.

#

Workflow

RESO Mapping

Key Tools

1

Listing Lifecycle

Property.StandardStatus

search_properties, get_property, list_media, get_property_coordinates

2

Lead-Contact Lifecycle

Contacts.ContactType funnel

search_opportunities, get_contact, search_tasks

3

Sales Pipeline

8-stage buyer journey

get_leads_by_property, get_lead_properties, list_viewings, list_offers, list_contracts

4

Showing / Viewing

ShowingAppointment

list_viewings, get_viewing, list_meetings

5

Transaction / Offer

TransactionManagement

list_offers, get_offer, list_contracts, get_contract

6

Activity / Follow-up

Engagement tracking

list_calls, list_meetings, list_email_messages, search_tasks

Status Mappings

Qobrix Property Status

RESO StandardStatus

available

Active

reserved

Pending / Under Contract

sold

Closed

withdrawn

Withdrawn / Canceled

Qobrix Opportunity Status

RESO Lead Funnel

new

MQL / Raw Lead

open

SQL / Active

won

Closed Won

closed_lost

Lost


Tools at a Glance

64 tools — CRM entities, schema discovery, analytics (qobrix_count, qobrix_top_values, qobrix_top_records, qobrix_aggregate), a flexible deals shortcut (qobrix_deals), reporting (qobrix_timeseries, qobrix_funnel, qobrix_rep_scorecard, qobrix_stale_leads, qobrix_win_loss, qobrix_days_on_market), customer intelligence (qobrix_cohort), audit / change history (qobrix_get_changes, qobrix_search_changes, qobrix_field_change_history, qobrix_top_field_changers), cache helpers (qobrix_cache_stats, qobrix_cache_clear), and session & identity (qobrix_whoami):

Entity Group

Tools

Capabilities

Properties

5

List, Get, Search, Coordinates (map), Properties-by-Lead

Contacts

3

List, Get, Search

Agents

3

List, Get, Search

Opportunities / Leads

5

List, Get, Search, Leads-by-Property, Lead-Properties

Property Viewings

3

List, Get, Search

Tasks

3

List, Get, Search

Media

2

List (with entity filter), Get (with size variants)

Projects

4

List, Get, Search, Coordinates

Offers

3

List, Get, Search

Contracts

3

List, Get, Search

Calls

2

List, Get

Meetings

2

List, Get

Email Messages

2

List, Get

Schema / Meta

3

Get Schema (field discovery), Get Field Options (enum values), Search DSL Help (full grammar + cheatsheets)

Analytics

4

Counts, top-N field values, full-scan top-N records by numeric/date, and sum/avg/min/max/count aggregates (with single- or multi-dim grouping). Prefer list/search sort for a single page; use top_records/aggregate for full-set scans or nullable fields

Deals

1

Flexible domain shortcut over the Contracts table (sales, rentals, listings, pipeline) with kind / contract_types[] / contract_statuses[] / date_field / min_price / party filters / summary block

Reporting

6

Time-series with YoY (qobrix_timeseries), canonical sales funnel + conversion % (qobrix_funnel), per-rep scorecard / agent leaderboard (qobrix_rep_scorecard), silent-lead detection (qobrix_stale_leads), win-rate analytics (qobrix_win_loss), days-on-market (qobrix_days_on_market)

Customers

1

Repeat-buyer / seller / lead cohorts (qobrix_cohort) — find contacts that appear on multiple closed deals or opportunities

Audit

4

Per-record change log (qobrix_get_changes), cross-resource change search (qobrix_search_changes), field-level history (qobrix_field_change_history), top field changers (qobrix_top_field_changers)

Cache

2

Stats and prefix or full invalidation for fresher reads

Session & identity

1

Current user profile (qobrix_whoami). Sign-in is User OAuth on the client.

Every tool description includes its canonical workflow role, RESO equivalent, verified include[] options, FK resolution guidance, and search expression examples.

Analytics & Deals usage examples

Server-side sort (OpenAPI sort[]) works for most fields — e.g. sort: "-list_selling_price_amount" on properties. Use qobrix_top_records / qobrix_aggregate when you need a full-dataset scan, or when a nullable field (e.g. opportunities.budget) returns no rows under server sort. "Closed deals" don't live as a property flag — they're rows in the Contracts table. The analytics/deals tools remove the need for client-side scripting:

// 1) Top 5 closed 2026 sales, sorted by final_selling_price_amount,
//    with property + agent + lawyers resolved to readable names.
{
  "tool": "qobrix_top_records",
  "args": {
    "resource": "contracts",
    "sort_by": "final_selling_price_amount",
    "search": "contract_type == \"cos\" and contract_status == \"agreed\" and date_of_contract >= \"2026-01-01\" and date_of_contract < \"2027-01-01\"",
    "top": 5
  }
}

// 2) 2026 sales volume, plus an agent leaderboard in one extra call.
{
  "tool": "qobrix_aggregate",
  "args": {
    "resource": "contracts",
    "field": "final_selling_price_amount",
    "op": "sum",
    "search": "contract_type == \"cos\" and contract_status == \"agreed\" and date_of_contract >= \"2026-01-01\" and date_of_contract < \"2027-01-01\"",
    "group_by": "commission_to_2",
    "top": 10
  }
}

// 3) Flexible "deals" shortcut — same answer as (1) with one default-laden call,
//    plus a full-set summary block (by_status, by_type, totals, median).
{ "tool": "qobrix_deals", "args": { "year": 2026, "top": 5 } }

// 4) Best 2026 rental contracts by final rental price.
{ "tool": "qobrix_deals", "args": { "kind": "rental", "year": 2026, "top": 5 } }

// 5) Under-contract reservations + closed sales together (pipeline + actuals).
{
  "tool": "qobrix_deals",
  "args": { "contract_statuses": ["reserved", "agreed"], "year": 2026 }
}

// 6) "My deals this year": uses the CURRENT_USER special var.
{
  "tool": "qobrix_deals",
  "args": { "assigned_to": "CURRENT_USER", "year": 2026 }
}

// 7) Monthly 2026 closed-sale volume with prior-year YoY %.
{
  "tool": "qobrix_timeseries",
  "args": {
    "resource": "contracts",
    "bucket": "month",
    "metric": "sum",
    "field": "final_selling_price_amount",
    "year": 2026,
    "search": "contract_type == \"cos\" and contract_status == \"agreed\"",
    "compare_to_prior": true
  }
}

// 8) Full 2026 sales funnel (Leads → Qualified → Viewing → Offer → Reserved → Closed).
{ "tool": "qobrix_funnel", "args": { "year": 2026 } }

// 9) 2026 agent leaderboard by volume (omit `user` for leaderboard mode).
{ "tool": "qobrix_rep_scorecard", "args": { "year": 2026, "sort_by": "volume", "top": 10 } }

// 10) Silent leads — open opportunities with no activity in 30 days.
{ "tool": "qobrix_stale_leads", "args": { "since_days": 30 } }

// 11) Multi-dim pivot: 2026 closed-sale volume by city × property_type.
{
  "tool": "qobrix_aggregate",
  "args": {
    "resource": "contracts",
    "field": "final_selling_price_amount",
    "op": "sum",
    "search": "contract_type == \"cos\" and contract_status == \"agreed\" and date_of_contract >= \"2026-01-01\" and date_of_contract < \"2027-01-01\"",
    "group_by": ["property_id", "contract_type"],
    "top": 10
  }
}

// 12) Repeat buyers — contacts behind 2+ closed sales in 2026.
{ "tool": "qobrix_cohort", "args": { "kind": "buyers", "year": 2026, "min_count": 2 } }

// 13) Win-rate by lead source in 2026, with top loss reasons resolved.
{
  "tool": "qobrix_win_loss",
  "args": { "year": 2026, "group_by": "source", "include_top_losses": true }
}

// 14) 2026 days-on-market by property type, with longest/shortest outliers.
{
  "tool": "qobrix_days_on_market",
  "args": { "kind": "sold", "year": 2026, "group_by": "property_type", "include_outliers": true }
}

Quick Start

git clone https://github.com/gca-ltd/qobrix-crm-mcp.git
cd qobrix-crm-mcp
npm install
npm run build

Configuration

Create a .env file in the project root:

QOBRIX_API_URL=https://yourcrm.qobrix.com
QOBRIX_API_USER=your-api-user-uuid
QOBRIX_API_KEY=your-api-key
QOBRIX_LOCALE=en-US          # optional

Variable

Required

Description

QOBRIX_API_URL

Yes (none)

Qobrix instance base URL

QOBRIX_API_USER

Yes (none)

X-Api-User header value (UUID)

QOBRIX_API_KEY

Yes (none)

X-Api-Key header value

QOBRIX_LOCALE

No

X-Locale header (e.g. en-US, el-GR)

Auth modes

Clone this package, run none or api_key, and put live Qobrix data in front of Claude, Cursor, or any MCP client — Apache 2.0.

Auth type

In this package?

When

How credentials arrive

none (default)

Yes

QOBRIX_MCP_TRANSPORT=stdio (or unset)

Shared QOBRIX_API_* from process env

api_key

Yes

TRANSPORT=http + QOBRIX_MCP_AUTH=api_key

Authorization: Bearer <user>:<key>, or X-Api-User / X-Api-Key (trusted callers; bind localhost)

signed-header (legacy)

Needs companion AS

TRANSPORT=http and QOBRIX_MCP_XCHAT_LEGACY left on

Self-service OAuth: MCP returns a /connect URL; user signs in at SharpSir’s Enterprise OAuth Authorization Server; this server holds the session

oauth_user

Needs companion AS

TRANSPORT=http + QOBRIX_MCP_AUTH=oauth_user

Remote MCP OAuth (RFC 9728 PRM + Bearer on /mcp) for Claude.ai / Desktop custom connectors and Dust.tt Spaces tools — same resource URL, per-user login

none and api_key are fully supported out of this package. The signed-header path and oauth_user require SharpSir’s separate Enterprise OAuth / SSO product — not distributed as part of this repo. oauth_user does not change none, api_key, or the signed-header path — select it when you want remote hosts such as Claude.ai or Dust.tt to drive OAuth themselves.

Enterprise OAuth

Need the agent to work as a signed-in Qobrix user — not a shared API key? the signed-header path is designed for that. It requires SharpSir’s Enterprise OAuth solution: a hosted Authorization Server bundle (login + 2FA + consent, per-user API-key minting, encrypted credential vault, audience-bound tokens) that pairs exclusively with this MCP server.

How the signed-header path works (MCP self-auth — northbound clients unchanged):

  1. A tool runs with no session → the MCP returns an authorization URL:

    • URL-mode elicitation (JSON-RPC -32042) when the client supports elicitation.url (Claude, Cursor, etc.)

    • A Markdown [Sign In to Qobrix](/connect?e=…) link in the tool result for clients without elicitation (e.g. ragchat / LangChain) — the LLM must relay it verbatim (unique / single-use; never reuse an older link)

  2. The user opens /connect on this server (anti-phishing indirection) → signed cookie + redirect to the Enterprise OAuth login page

  3. After login + 2FA + consent, the AS redirects to /oauth/callback; this MCP exchanges the code (PKCE), introspects for Qobrix credentials, and stores them in an encrypted session vault

  4. The next tool call runs authenticated. On Qobrix 401/403, the vault is cleared and a fresh /connect URL is returned

  5. Agents call qobrix_whoami to confirm the signed-in account. Sign-in is the client's OAuth flow, not a tool.

  • Not available as a public download and not something you can clone from GitHub.

  • Delivered and configured by our team upon request as an enterprise solution bundle.

  • No third-party OAuth servers — the signed-header path is hard-wired to this Enterprise OAuth solution only.

  • Security: the signed-header path uses per-user encrypted session vaults (keyed by chat identity headers) and leaves /mcp without a client bearer. Bind QOBRIX_MCP_HOST=127.0.0.1 and set QOBRIX_MCP_IDENTITY_SECRET (shared only with the trusted MCP host like ragchat) so identity headers cannot be forged. Keep vault encryption on QOBRIX_MCP_STATE_SECRET (MCP-only). If you reverse-proxy for browsers, publish only /connect and /oauth/callback — deny public /mcp and /health. Local agents (ragchat) call http://127.0.0.1:<port>/mcp. When ALLOWED_HOSTS lists only the public hostname, loopback Host values (127.0.0.1 / localhost / ::1) are auto-added if the server binds to loopback. Connect cookie Path follows PUBLIC_URL pathname; Express trust proxy is 2 behind Cloudflare→Apache. Deliver /connect links only to the individual user — never into a shared/group thread.

Ready to upgrade? Contact SharpSir Group · dev@sharpsir.group and ask for the Qobrix CRM MCP Enterprise OAuth bundle.

Once delivered, you point this server at the issuer you receive:

export QOBRIX_MCP_TRANSPORT=http
export QOBRIX_MCP_AUTH=oauth
export QOBRIX_MCP_HOST=127.0.0.1
export QOBRIX_MCP_PORT=3502
export QOBRIX_MCP_PUBLIC_URL=http://127.0.0.1:3502
export QOBRIX_MCP_RESOURCE_URL=http://127.0.0.1:3502/mcp
export QOBRIX_OAUTH_ISSUER=<issuer-from-enterprise-bundle>
export QOBRIX_OAUTH_INTROSPECTION_SECRET=<shared-secret-from-bundle>
export QOBRIX_MCP_STATE_SECRET=<16+-char-secret>
export QOBRIX_MCP_IDENTITY_SECRET=<16+-char-secret-shared-with-ragchat>
export QOBRIX_MCP_DATA_DIR=./data/mcp-oauth
export QOBRIX_MCP_ALLOWED_HOSTS=qobrix-mcp.example.com   # loopback Hosts auto-added when HOST is 127.0.0.1
npm start

the signed-header path endpoints (after the Enterprise OAuth solution is paired):

  • GET /connect?e=… — start authorization (sets cookie, 302 to AS)

  • GET /oauth/callback — PKCE code exchange + per-user session vault write

  • GET /health — includes connected and session_vaults count

  • Unauthenticated /mcp is intentional for northbound clients: tools surface the connect URL when needed — keep /mcp on localhost in production

See docs/USER_GUIDE.md for none → B → C step-by-step, reverse-proxy lockdown, and Host allowlist details.

For ragchat / the signed-header path, register the remote MCP URL (…/mcp) as a normal Streamable HTTP server (no client-side OAuth provider required); the MCP handles auth via /connect. Keep /mcp on localhost in that topology.

oauth_user — Claude.ai and Dust.tt remote MCP (shared resource)

Use a separate MCP process (or host) with QOBRIX_MCP_AUTH=oauth_user. Remote hosts drive OAuth themselves against the same HTTPS /mcp URL:

Host

How to connect

Auth

Claude.ai / Claude Desktop

Settings → Connectors → Add custom connector

Automatic DCR + PKCE (redirect https://claude.ai/api/mcp/auth_callback)

Dust.tt

Spaces → Tools → Add MCP Server

Prefer Automatic; Static OAuth fallback — see INSTALL — Connect Dust

  1. User pastes https://intranet.sharpsir.group/qobrix-crm/mcp into Claude or Dust

  2. Host hits /mcp → receives 401 + WWW-Authenticate: Bearer resource_metadata=…

  3. Host fetches /.well-known/oauth-protected-resource → discovers QOBRIX_OAUTH_ISSUER

  4. Host completes OAuth (DCR or Static) + PKCE against the Enterprise OAuth AS

  5. Subsequent /mcp calls send Authorization: Bearer <access_token>; this server introspects and runs tools as that Qobrix user

Claude and Dust share one oauth_user stack (same MCP resource + same Authorization Server). Each host registers its own OAuth client; each member logs into Qobrix as themselves.

export QOBRIX_MCP_TRANSPORT=http
export QOBRIX_MCP_AUTH=oauth_user
export QOBRIX_MCP_HOST=127.0.0.1
export QOBRIX_MCP_PORT=3502
export QOBRIX_MCP_ALLOWED_HOSTS=intranet.sharpsir.group
export QOBRIX_MCP_PUBLIC_URL=https://intranet.sharpsir.group/qobrix-crm
export QOBRIX_MCP_RESOURCE_URL=https://intranet.sharpsir.group/qobrix-crm/mcp
export QOBRIX_OAUTH_ISSUER=https://intranet.sharpsir.group/qobrix-crm/mcp-oauth
export QOBRIX_OAUTH_INTROSPECTION_SECRET=<shared-secret-from-bundle>
npm start

On the AS, when using a redirect allowlist, keep Claude’s callback and append exact Dust finalize URLs (never replace Claude’s entry). Cursor also needs https://www.cursor.com/agents/mcp/oauth/callback in addition to cursor:// (DCR sends both plus http://localhost:8787/callback):

export QOBRIX_OAUTH_REDIRECT_ALLOWLIST=https://claude.ai/api/mcp/auth_callback,http://127.0.0.1,http://localhost,cursor://,https://www.cursor.com/agents/mcp/oauth/callback,https://eu.dust.tt/oauth/mcp/finalize,https://eu.dust.tt/oauth/mcp_static/finalize,https://dust.tt/oauth/mcp/finalize,https://dust.tt/oauth/mcp_static/finalize,https://app.dust.tt/oauth/mcp/finalize,https://app.dust.tt/oauth/mcp_static/finalize

Publish HTTPS /mcp + PRM (and the AS) to the public internet; allowlist Anthropic egress 160.79.104.0/21 if WAF’d, and allow Dust egress in addition — do not remove Claude’s allowlist. the signed-header path’s loopback/deny public /mcp guidance stays valid for ragchat deployments — do not flip that topology for the signed-header path processes.

Full steps: INSTALL — Connect Claude · INSTALL — Connect Dust · Dust: Adding an MCP Server.

Caching

All MCP tools are read-only GETs, so a response cache cannot corrupt CRM state. The server wraps one chokepoint (QobrixClient.request()) with a read-through cache, so every list/get/search/schema call — including each page of a relevance max_scan — is cached. Boost scoring is post-fetch and does not change the cache key, so re-ranking with different boost[] reuses the same candidate pages.

Design — cache-aside with single-flight coalescing:

  • Tier 1 — in-memory LRU (always on, zero deps): per-process, TTL'd, size-capped.

  • Tier 2 — Redis (optional, lazy-loaded via dynamic import()): set QOBRIX_REDIS_URL to enable; the server falls back to memory-only on any Redis error.

  • Single-flight: when the LLM fires parallel tool calls that hit the same cold cache key (common with qobrix_top_values), all in-process callers share one upstream fetch.

  • Errors are never cached — a transient 5xx will not get stuck.

  • TTL only, no stale-while-revalidate in v1.

Environment variables:

Variable

Default

Description

QOBRIX_CACHE_ENABLED

true

Set to false to bypass the cache entirely

QOBRIX_CACHE_TTL

300

TTL in seconds; CRM edits visible within this window

QOBRIX_CACHE_MAX_ENTRIES

5000

LRU cap for the in-memory tier

QOBRIX_REDIS_URL

(empty)

redis:// / rediss:// URL; empty = memory only

QOBRIX_REDIS_KEY_PREFIX

qobrix:

Namespace when sharing a Redis instance

Cache tools (exposed to the LLM):

Tool

Use

qobrix_cache_stats

Hits/misses/size/in-flight/Redis status — verify the cache is paying off

qobrix_cache_clear

Invalidate all keys or by prefix (e.g. v1:request:opportunities) for instant refresh before TTL

Recommended Redis server config (for a dedicated cache-only Redis, per Redis docs):

maxmemory 256mb
maxmemory-policy allkeys-lru
maxmemory-samples 10

TTL guidance — Redis docs recommend short TTLs for frequently-changing data (60–120s) and longer for stable data (hours). 300s is a conservative default for a CRM that mixes lead pipeline (changes minutely) with property listings (changes hourly). Use qobrix_cache_clear when you need an instant refresh.

Trade-off / known limit: Single-flight coalescing is in-process only. Multi-instance deployments behind one shared Redis can still see modest stampede on cold keys; a distributed SETNX lock is future work and not needed for single-user MCP clients.

Best-practices alignment:

Best practice

Where honored

Cache-aside / read-through (Redis docs, MCP caching guides)

QobrixClient.request() wrap

Canonical, versioned cache key

cacheKey("v1", ...) with sorted params

Conservative TTL

300s default, env-overridable

Errors not cached

Wrap stores only on resolved upstream success

Single-flight stampede prevention

In-process inflight map

allkeys-lru for cache-only Redis

Documented above for self-hosters

Observability + manual invalidation

qobrix_cache_stats, qobrix_cache_clear

Official Node.js Redis client

redis (node-redis), as optionalDependencies

Cursor IDE setup

none is a local stdio process. oauth_user is the hosted URL https://intranet.sharpsir.group/qobrix-crm/mcp (same resource Claude and Dust use). For oauth_user, see INSTALL — Connect Cursor.

This stdio path uses a local node process. Cursor discovers servers from project or user mcp.json: .cursor/mcp.json inside the folder you opened, or ~/.cursor/mcp.json for all workspaces.

1. Prerequisites

  • Node.js 20+ on the machine where Cursor runs the MCP (local laptop or remote SSH host).

  • Clone this repo, install, and build (see Quick Start).

  • dist/index.js must exist (npm run build) before adding the MCP entry.

2. Credentials

  1. Copy the template: cp .env.example .env

  2. Edit .env and set at least QOBRIX_API_URL, QOBRIX_API_USER, and QOBRIX_API_KEY (see Configuration).

  3. Keep .env out of git; it is listed in .gitignore.

3. Where to put the JSON

Location

When to use

<project>/.cursor/mcp.json

You opened that project folder in Cursor; teammates can commit a template (without secrets) or you keep it local-only.

~/.cursor/mcp.json

Same MCP on every workspace on that machine.

Merge your entry into the existing "mcpServers" object; do not replace the whole file if you already have other servers.

Pass absolute paths so it works the same whether the workspace root is this repo or a parent folder (and so SSH remote paths resolve correctly).

{
  "mcpServers": {
    "qobrix-crm-mcp": {
      "command": "node",
      "args": [
        "--env-file=/absolute/path/to/qobrix-crm-mcp/.env",
        "/absolute/path/to/qobrix-crm-mcp/dist/index.js"
      ],
      "description": "Read-only Qobrix CRM MCP"
    }
  }
}

Why this pattern:

  • Credentials stay in .env, not in JSON.

  • Node loads the file before your server starts, so process.env is populated even when the host’s envFile field is ignored or behaves inconsistently for stdio servers.

5. Alternative: inline env

Useful if you cannot use --env-file (older Node). Secrets live in mcp.json — restrict file permissions and do not commit them.

{
  "mcpServers": {
    "qobrix-crm-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/qobrix-crm-mcp/dist/index.js"],
      "env": {
        "QOBRIX_API_URL": "https://yourcrm.qobrix.com",
        "QOBRIX_API_USER": "your-api-user-uuid",
        "QOBRIX_API_KEY": "your-api-key",
        "QOBRIX_LOCALE": "en-US"
      }
    }
  }
}

You can also use Cursor’s config interpolation (for example ${env:QOBRIX_API_KEY}) so values are injected from your OS environment instead of literals.

6. Optional: envFile in MCP JSON

Cursor supports an envFile property for stdio servers. Some setups do not pass those variables into the child process reliably; if tools fail with “Missing required environment variables”, switch to --env-file as in step 4.

7. After editing mcp.json or .env

  1. Reload MCP — Command Palette → MCP restart, or reload the Cursor window.

  2. Check logs — View → Output → pick “MCP” / “MCP Logs” in the dropdown; fix path or Node errors there.

  3. Tool approval — By default Cursor asks before each tool call; you can allow auto-run for trusted tools in Cursor settings if you prefer.

Other MCP hosts

Claude.ai / Claude Desktop (oauth_user) — remote custom connector at https://intranet.sharpsir.group/qobrix-crm/mcp. See oauth_user and INSTALL — Connect Claude.

Dust.tt (oauth_user) — Spaces → Tools → Add MCP Server with the same URL. Prefer Automatic auth and Personal accounts. See INSTALL — Connect Dust.

Claude Desktop / Cursor (none stdio) — same stdio shape: command + args to node and either --env-file or env in the host’s MCP config file.

CI / headless — run node --env-file=.env dist/index.js with a stdio MCP client library; ensure .env is supplied via secrets, not committed.


Search Expression Syntax

Tools that accept a search parameter use Qobrix's Symfony Expression Language (OpenAPI SearchExpression). Call qobrix_search_dsl_help for the full grammar + property/project field cheatsheets (optionally with live schema field names).

Feature

Syntax

Example

Equality

==, !=, <>

status == "available"

Comparison

<, >, <=, >=

list_selling_price_amount <= 500000

Contains

contains, starts with, ends with

city contains "Limas"

Set membership

in [...], not in [...]

property_type in ["villa","house"]

Range

in min..max

bedrooms in 2..4

Logical

and, or, not, parentheses

status == "available" and sale_rent == "for_sale"

Date helpers

DAYS_AGO(n), MONTHS_AGO(n), DAYS_FROM_NOW(n), …

created >= DAYS_AGO(30)

Time shortcuts

NOW, TODAY, THIS_WEEK, LAST_MONTH, THIS_YEAR, …

created >= LAST_MONTH

Current user

CURRENT_USER

assigned_to == CURRENT_USER

Geo / misc

DISTANCE_FROM, IN_POLYGON, TRANSLATED, MIN/MAX

DISTANCE_FROM(coordinates, "34.43,32.13") <= 5000

Association path

Entity.field

SalespersonUsers.Contacts.country == "CY"

Tip: Call qobrix_search_dsl_help({ resource: "Properties" }) before composing free-language demand into a query. Use qobrix_get_field_options for enum values and qobrix_get_schema for the full field list.

Relevant search on all resources (F1)

Every qobrix_search_* tool (properties, projects, contacts, agents, opportunities, viewings, tasks, offers, contracts) uses a two-tier design so free-language demand maps to high precision and high recall:

  1. search — hard must-haves (server-side DSL filter → precision floor).

  2. boost[] — soft weighted nice-to-haves scored in-process over a candidate pool (recall + ranking).

  3. limit — how many ranked rows to return (default 10, max 100). Raise for more options; keep modest to avoid context overload.

  4. max_scan — candidate pool when boosting (default 100, hard cap 500). Higher improves recall; each scanned page is response-cached.

With boost, each row includes _relevance (score) and _matched (which clauses hit); pagination.mode is "ranked". Without boost, a single cached list page is returned (mode: "fast").

qobrix_search_properties({
  search: 'status == "available" and sale_rent == "for_sale"',
  boost: [
    { field: "sea_view", op: "==", value: true, weight: 3 },
    { field: "bedrooms", op: ">=", value: 3, weight: 2 },
    { field: "list_selling_price_amount", op: "in", value: "200000..600000", weight: 2 },
  ],
  limit: 15,
  max_scan: 200,
});

Lead ↔ listing matching via search (2-way)

  • Demand → supply: take a lead's criteria → qobrix_search_properties / qobrix_search_projects with search+boost. Native: qobrix_get_properties_by_lead / qobrix_get_lead_properties.

  • Supply → demand: qobrix_search_opportunities with open-lead search + boost against the listing (works for projects too). Native for properties only: qobrix_get_leads_by_property.

// Who wants a Limassol 3-bed ~€400k listing?
qobrix_search_opportunities({
  search: 'status in ["new","open"] and buy_rent == "buy"',
  boost: [
    { field: "area_of_interest", op: "contains", value: "Limassol", weight: 3 },
    { field: "bedrooms_from", op: "<=", value: 3, weight: 2 },
    { field: "list_selling_price_to", op: ">=", value: 400000, weight: 2 },
  ],
  limit: 15,
  max_scan: 200,
});

Boost operators: == != < > <= >= in contains starts_with ends_with. For ranges use op: "in" with value: "min..max".

Search (and every other list/get) shares the global cache TTL (QOBRIX_CACHE_TTL, default 300s). After CRM edits, refresh with qobrix_cache_clear({ prefix: "v1:request:properties" }) (or opportunities, projects, …).


Three strategies to resolve foreign keys:

  1. include[] parameter — expand associations inline in one call

qobrix_get_property({ id: "...", include: ["Agents", "PropertyViewings"] })
  1. Separate get call — take the UUID from an FK field and call the appropriate tool

// property.agent → UUID
qobrix_get_agent({ id: "<agent-uuid>" })
  1. Search by FK — find related records via search expression

qobrix_search_properties({ search: 'agent == "<agent-uuid>"' })

Only include[] values marked Verified in tool descriptions are guaranteed to work. When include[] is unavailable for an association, use search-by-FK.


Payload defaults

To keep tool outputs short enough for the calling LLM's context window, list / search / get tools default to compact payloads:

Param

Default

Effect when default

expand

false

Foreign keys come back as UUID strings instead of being expanded into nested objects. Resolve them on demand with the matching get tool or with a targeted include[].

media

false

Inline media (photos, floor plans, thumbnail URLs) is not attached to list rows. Use qobrix_list_media({ related_model: 'Properties', related_id: '<uuid>' }) when media is actually needed.

Override per call only when the caller actually needs the heavier payload:

// Cheap browse — recommended for most reporting / pipeline calls
qobrix_list_properties({ limit: 10 });

// Heavy detail — only when the LLM truly needs nested FKs + media URLs
qobrix_list_properties({ limit: 5, expand: true, media: true });

// Prefer surgical include[] over full expand=true:
qobrix_get_property({ id: "...", include: ["AgentAgents", "ProjectProjects"] });

This change typically shrinks qobrix_list_properties({ limit: 10 }) from ~300 KB to ~5–10 KB.


Output cap

Every tool result is capped at QOBRIX_MCP_MAX_RESULT_CHARS characters of rendered JSON (default 30 000, roughly 7.5 K tokens). Behaviour:

  • Paginated payloads ({ data: [...], pagination: {...} }): truncated to the largest prefix of data[] that fits, and a _truncated block is attached with kept_rows, omitted_rows, original_chars, max_chars, and a hint telling the LLM how to scope the next call. If nested expand/media objects alone blow the cap, rows are compacted to scalars (_truncated.compacted: true) so at least one usable row is returned.

  • Grossly oversized (default: original size > 8 × the cap, override QOBRIX_MCP_REFINE_MULTIPLIER): returns status: "result_too_large" with _refine_required (assistant instruction + suggested narrowing + small returned_sample) so the LLM asks the user to reformulate — not dump.

  • Non-paginated payloads (single get, custom analytic shapes): the JSON is clipped at the cap and a QOBRIX_MCP TRUNCATED trailer is appended (or the same refine directive when grossly oversized).

When boost is used with expand=true or media=true, max_scan is auto-capped at 100 and pagination.scan_capped_reason may be "expand/media".

Override the cap / refine threshold:

QOBRIX_MCP_MAX_RESULT_CHARS=60000
QOBRIX_MCP_REFINE_MULTIPLIER=8

If you regularly hit the cap or refine guard, use fields[] (whitelist columns), a tighter search expression, a smaller limit, or keep expand=false / media=false.


Testing

The project includes 226 automated tests across 63 describe suites (integration, multi-step scenarios, RESO workflows, cache, relevance, output-cap, client-sort, and OAuth mode smoke):

# Integration tests — individual tool mechanics
npm test

# Scenario tests — multi-step tool chains (19 real-world scenarios)
npm run test:scenarios

# Workflow tests — canonical RE business processes (8 RESO-aligned suites)
npm run test:workflows

# Cache tests — read-through, single-flight, LRU eviction, search-page keys (no API needed)
npm run test:cache

# Relevance tests — boost scoring, DSL help, search cache keys (no API needed)
npm run test:relevance

# Format tests — output cap + truncation behaviour (no API needed)
npm run test:format

# OAuth modes smoke — api_key header rejection + the signed-header path /connect elicitation path
npm run test:oauth-modes

# Run everything
npm run test:all

Suite

Tests

Coverage

Integration

70

Every tool, pagination edge cases, include/fields mechanics, analytics + reporting tools

Scenarios

55

Agent morning brief, buyer search, lead triage, FK chains, pipeline reports

Workflows

39

Listing lifecycle, lead funnel, sales pipeline, showing, transaction, media, activity, schema

Cache

22

Read-through cache, single-flight coalescing, LRU eviction, key canonicalization, search-page keys (no live API)

Relevance

23

Boost eval/score/rank (incl. opportunity/contact shapes), fields[]+boost union, DSL help text, search cache-key stability (no live API)

Format

7

formatResult output cap, paginated truncation, expand/media compaction (kept_rows>=1), result_too_large refine guard, fallback trailer, env override (no live API)

Client sort

7

normalizeSort + buildQobrixUrl emit OpenAPI sort[]= (not scalar sort= that Qobrix ignores)

OAuth modes

4

api_key headers, the signed-header path /connect, oauth_user PRM/401/Bearer


Architecture

src/
├── index.ts          # MCP server entry point + RESO workflow instructions
├── http.ts           # Streamable HTTP transport (api_key, oauth_user, signed-header)
├── auth-types.ts     # Public auth types: none, api_key, oauth_user
├── client.ts         # QobrixClient — HTTP + read-through response cache
├── auth-context.ts   # AsyncLocalStorage per-request credentials
├── oauth-client.ts   # the signed-header path self-service OAuth client + session vault
├── oauth-rs.ts       # Companion AS metadata + introspection helpers
├── request-context.ts# ALS for McpServer (elicitation capability detection)
├── cache.ts          # LRU memory tier, optional Redis, single-flight coalescing
├── relevance.ts      # Boost scoring + cached candidate pager for search
├── search-dsl.ts     # Full SearchExpression DSL reference + field cheatsheets
├── types.ts          # TypeScript interfaces
├── schemas.ts        # Zod schemas with rich LLM-facing descriptions
└── tools/
    ├── index.ts      # Tool registration hub + formatResult / errorResult
    ├── properties.ts # Listing Lifecycle + relevance search
    ├── contacts.ts   # Lead-Contact Lifecycle tools
    ├── agents.ts     # RESO Member tools
    ├── opportunities.ts # Sales Pipeline tools
    ├── viewings.ts   # Showing Lifecycle tools
    ├── tasks.ts      # Follow-up & Pipeline Management tools
    ├── media.ts      # Media Lifecycle tools
    ├── projects.ts   # Project/Development + relevance search
    ├── offers.ts     # Transaction Lifecycle tools
    ├── contracts.ts  # Transaction close tools
    ├── activities.ts # Activity Tracking (calls, meetings, emails)
    ├── analytics.ts  # qobrix_count, qobrix_top_values, qobrix_top_records, qobrix_aggregate
    ├── deals.ts      # qobrix_deals (flexible Contracts shortcut)
    ├── reports.ts    # qobrix_timeseries (bucketed metric + YoY), qobrix_days_on_market
    ├── pipeline.ts   # qobrix_funnel, qobrix_stale_leads, qobrix_win_loss
    ├── productivity.ts # qobrix_rep_scorecard
    ├── customers.ts  # qobrix_cohort (repeat buyers/sellers/leads)
    ├── cache.ts      # qobrix_cache_stats, qobrix_cache_clear
    ├── audit.ts      # change log / field history / top changers
    └── meta.ts       # Schema discovery + qobrix_search_dsl_help
test-suite/
├── integration.test.mjs  # Live API smoke tests
├── scenarios.test.mjs    # Multi-step CRM scenarios
├── workflows.test.mjs    # RESO workflow coverage
├── cache.test.mjs        # Cache unit tests (incl. search-page keys)
├── relevance.test.mjs    # Boost scoring + DSL help unit tests
├── format.test.mjs       # Output-cap / truncation tests
└── oauth-modes.test.mjs  # api_key/C auth smoke tests

How the LLM Learns

The server teaches the LLM at three levels:

  1. Server instructions — top-level instructions field in the MCP initialize response provides the full data model, six canonical workflows with tool recipes, search syntax, FK resolution strategies, and known quirks.

  2. Tool descriptions — each tool description includes its canonical workflow role, RESO equivalent, verified include[] options, FK field mappings, response shape, and search examples. Relevance search tools document the two-tier search + boost recipe; qobrix_search_dsl_help exposes the full DSL on demand.

  3. Parameter descriptions — Zod schemas provide per-parameter help with concrete examples, valid enum values, and cross-tool references.


Technology

Component

Technology

Runtime

Node.js ≥ 20

Language

TypeScript 5.7

MCP SDK

@modelcontextprotocol/sdk 1.26

Validation

Zod 3.24

Optional cache

redis 4.x (node-redis) when QOBRIX_REDIS_URL is set

Transport

stdio (default) · Streamable HTTP (api_key, oauth_user, signed-header)

API Auth

none and api_key: X-Api-User + X-Api-Key · the signed-header path: self-service Enterprise OAuth (/connect URL)

Testing

Node.js built-in test runner (node:test)

License

Apache License 2.0 — Copyright 2025–2026 SharpSir Group

none and api_key are included in this open-source package. The signed-header path pairs with SharpSir’s Enterprise OAuth Authorization Server (SSO / per-user identity) — a separate commercial product delivered upon request — sharpsir.group · dev@sharpsir.group.


Available Tools

64 tools
qobrix_aggregateA

Sum / avg / min / max / count of a numeric field across all matching records — optionally grouped by another field (top-N buckets). Paginates server-side (cap 20,000 rows) and computes in-process, which is how to get totals on Qobrix fields the API won't sort. Example uses: Total 2026 closed-sale volume: resource='contracts', field='final_selling_price_amount', op='sum', search='contract_type == "cos" and contract_status == "agreed" and date_of_contract >= "2026-01-01" and date_of_contract < "2027-01-01"'. Agent leaderboard by volume: same search, group_by='commission_to_2', top=5. Average list price by property type: resource='properties', field='list_selling_price_amount', op='avg', group_by='property_type', search='status == "available"'.

Inputs: op (required), field (required), resource (required), top, +3 more.

qobrix_cache_clearA

Invalidate cached Qobrix responses. Use this when CRM data was edited in Qobrix and you need fresh reads before the TTL (default 300s) expires. Without 'prefix': clears the whole cache (memory + Redis). With 'prefix': clears only matching keys, e.g. 'v1:request:opportunities' to refresh lead lists, or 'v1:request:properties' to refresh listings. Returns the number of cleared entries in each tier.

Inputs: prefix.

qobrix_cache_statsA

Inspect the response cache: hits/misses, in-memory size, in-flight coalesced requests, TTL (seconds), and Redis tier status. Use to verify the cache is paying off (high hits, low misses on repeated workflows) or to diagnose why a query feels slow (cold cache, Redis degraded). Stats are process-local; restart resets them.

Inputs: no named parameters.

qobrix_cohortA

Find contacts that appear on multiple deals or opportunities — the 'repeat customer' report every CRM analyst gets asked for. Three modes: 'buyers' (default) walks closed contracts → opportunity_id → opportunities.contact_name to identify the buyer behind each closed sale. 'sellers' walks contracts → property_id → properties.seller. 'leads' groups all opportunities by contact_name regardless of close. Returns each repeat contact with deal_count, total_volume, total_commission, first_deal, last_deal, and a deals[] breakdown. Example uses: 2026 repeat buyers: { kind: 'buyers', year: 2026, min_count: 2 }. All-time loyal customers: { kind: 'buyers', min_count: 3 }. Recurring sellers this year: { kind: 'sellers', year: 2026 }. Frequent enquirers (any close): { kind: 'leads', since_days: 365 }.

Inputs: to, top, from, kind, +5 more.

qobrix_countA

Count records matching a search expression — returns just the total count, no data payload. Faster and lighter than fetching records when you only need a number. Example uses: Total active sale listings: resource='properties', search='status == "available" and sale_rent == "for_sale"'. Q1 closed-won opportunities: resource='opportunities', search='status == "closed_won" and last_status_change >= "2026-01-01" and last_status_change < "2026-04-01"'. Contacts created this month: resource='contacts', search='created >= THIS_MONTH'.

Inputs: resource (required), search.

qobrix_days_on_marketA

Days-on-market analytics. Joins Contracts to Properties on property_id and computes the (close_date - listing_date) duration in days, then aggregates count / mean / median / p75 / p90 / min / max. Defaults: kind='sold' (cos + agreed), listing_date_field='listing_date' with per-row fallback to website_listing_date then created. Use group_by to break down by property_type, city, agent, or commission_to_2 (broker). Set include_outliers=true for the 5 longest and 5 shortest deals. Example uses: Overall DOM for 2026 sales: { kind: 'sold', year: 2026 }. DOM by property type: { kind: 'sold', year: 2026, group_by: 'property_type' }. DOM by city (with outliers): { year: 2026, group_by: 'city', include_outliers: true }. Reservation cycle: { kind: 'reserved', year: 2026 }.

Inputs: to, top, from, kind, +6 more.

qobrix_dealsA

Flexible 'deals' tool over the Contracts table — covers sales, rentals, listing agreements, and pipeline reservations in a single call. Defaults: kind='sale', contract_statuses=['agreed'] (so a no-arg call still means 'closed sale contracts'). Every default is overridable via kind / contract_types / contract_statuses / include_reserved / date_field / min_price / max_price / party filters / raw search. Sorts in-process across the matching set (for a single page of highest prices you can also use list/search with sort='-final_selling_price_amount') and resolves property_id, agent, commission_to, lawyers, bank, etc. to readable names. Example uses: Best 2026 closed sales: { year: 2026, top: 5 }. Best 2026 rentals: { kind: 'rental', year: 2026, top: 5 }. Under-contract pipeline value: { contract_statuses: ['reserved'], year: 2026 }. My deals: { assigned_to: 'CURRENT_USER', year: 2026 }. All revenue deals >€1M last 90 days: { kind: 'any_revenue', since_days: 90, min_price: 1000000 }. Agent leaderboard (by commission): { year: 2026, by: 'commission', top: 10 }.

Inputs: by, to, top, desc, +15 more.

qobrix_field_change_historyA

Build a chronological timeline of edits to one field on one CRM record. Paginates GET /{resource}/{id}/changes and filters rows where the field appears in original or changed. Returns [{timestamp, user_id, user_name?, type, before, after}] sorted oldest-first. Use for: 'who changed source on this opportunity and when?'

Inputs: id (required), field (required), resource (required), max_pages, +1 more.

qobrix_funnelA

Canonical sales funnel in one call. Six stages with conversion %: Leads (opportunities created in window) → Qualified (status in [open,won]) → Viewing (property-viewings created in window) → Offer (offers created in window) → Reserved (contracts with contract_status=reserved) → Closed (contracts cos + agreed). Each stage is scoped by the same date window and (optional) assigned_to / agent. Use stage_overrides to substitute a tenant-specific definition for any stage. Example uses: 2026 funnel: { year: 2026 }. My funnel last 90 days: { assigned_to: 'CURRENT_USER', since_days: 90 }. Agent Vera's funnel: { agent: '', year: 2026 }.

Inputs: to, from, year, agent, +3 more.

qobrix_get_agentA

Get a single agent by UUID — resolve a RESO Member/ListAgent from a listing or lead. Returns { data: { id, ref, ... } }. Verified include: PrimaryContactContacts, User, Brands, AgencyAgents (parent brokerage). Agent 360° pattern: 1. Active listings: qobrix_search_properties with agent == '' and status == "available" 2. Active leads: qobrix_search_opportunities with agent == '' and status == "open" 3. Pipeline deals: qobrix_search_offers / qobrix_search_contracts linked to agent's opportunities 4. Activity: search Tasks, Calls, Meetings by the agent FK

Inputs: id (required), include.

qobrix_get_callA

Get a single call record by UUID — call detail in the Activity Tracking workflow. Returns { data: { id, ... } }. Include: AssignedToUsers, ContactContacts, RelatedOpportunityOpportunities, RelatedAgentAgents, CreatedByUsers.

Inputs: id (required), include.

qobrix_get_changesA

Retrieve audit / change-log entries for a single CRM record (Qobrix LogAudit). Calls GET /api/v2/{resource}/{id}/changes. Each row has original (before), changed (after), user_id (who), timestamp, type (create/update/delete). IMPORTANT: LogAudit.source is the resource name (e.g. 'Opportunities'), NOT the lead marketing source field. Field-level before/after for e.g. lead source are keys inside original and changed objects. For a filtered timeline of one field use qobrix_field_change_history.

Inputs: id (required), resource (required), page, sort, +3 more.

qobrix_get_contactA

Get a single contact by UUID — the person/company detail view in the Lead-Contact Lifecycle. Returns { data: { id, first_name, last_name, email, phone, ... } }. Verified include: AssignedToUsers (RESO OwnerMember), User, Language, Organizations. To build the full contact journey (canonical 360° view): 1. Leads: qobrix_search_opportunities with contact_name == '' 2. Tasks: qobrix_search_tasks with contact == '' 3. Calls: qobrix_search_calls (search by contact FK) 4. Meetings: qobrix_search_meetings (search by contact FK) 5. Seller listings: qobrix_search_properties with seller == '' This contact 360° pattern is the core of Lead-Contact Lifecycle and Follow-up workflows.

Inputs: id (required), include.

qobrix_get_contractA

Get a single contract by UUID — the deal close detail view in the Transaction Lifecycle. Returns { data: { id, ... } }. Verified include: Contacts (buyer/seller), PropertyIdProperties (the listing), OpportunityIdOpportunities (the lead), PaymentInstallments (payment schedule), ContractParties (all signatories), CreatedByUsers. Use include=['PaymentInstallments'] to audit the payment timeline.

Inputs: id (required), include.

qobrix_get_email_messageA

Get a single email message by UUID — email detail in the Activity Tracking workflow. Returns { data: { id, ... } }. Include: Properties, ContactContacts, RelatedOpportunityOpportunities, Agent, Campaign.

Inputs: id (required), include.

qobrix_get_field_optionsA

Get dropdown/enum field options — discover valid values for status fields, types, and categories. Essential for building correct search expressions in any workflow. Returns { data: [...], pagination: { ... } }. Each item has: id, resource, field, label, value. Key examples for canonical workflows: Listing statuses: search='resource == "Properties" and field == "status"' → available/reserved/sold/withdrawn. Lead statuses: search='resource == "Opportunities" and field == "status"' → new/open/won/closed_lost. Property types: search='resource == "Properties" and field == "property_type"'. Task types: search='resource == "Tasks" and field == "task_type"'. Pair with qobrix_get_schema for complete field discovery.

Inputs: page, limit, search.

qobrix_get_lead_propertiesA

Get properties linked to a lead — the 'demand-side matching' tool in the Sales Pipeline. Answers: 'What properties does this buyer want?' by returning properties attached to an opportunity. This is the canonical property suggestion pattern (RESO Demand/Solution matching). Reverse of qobrix_get_leads_by_property (which finds leads for a property). For free-language / ranked demand→supply (incl. projects), use qobrix_search_properties / qobrix_search_projects with search+boost derived from the lead's criteria. In the 8-stage buyer journey, use at the Solution/Viewing stage to prepare showing candidates. The id is the Opportunity UUID.

Inputs: id (required).

qobrix_get_leads_by_propertyA

Get matching leads for a property — the 'supply-side matching' tool in the Sales Pipeline. Answers: 'Who is interested in this listing?' by returning leads whose criteria match the property. This is the canonical buyer-property matching pattern (RESO Prospecting). Use after listing a new property to find potential buyers in the existing lead database. Complement with qobrix_get_lead_properties for the reverse (demand-side) match, or qobrix_search_opportunities with boost for ranked matching (incl. projects). The propertyId is the Property UUID. Returns paginated results.

Inputs: propertyId (required), page, limit.

qobrix_get_mediaA

Get a single media item by UUID — media detail view in the Media Lifecycle. Returns media metadata including URL/path. Optionally specify size variant for images: 'thumbnail', 'medium', 'large'. Use 'thumbnail' for preview grids, 'large' for full-resolution display.

Inputs: id (required), size.

qobrix_get_meetingA

Get a single meeting by UUID — meeting detail in the Activity/Showing Lifecycle. Returns { data: { id, ... } }. Include: AssignedToUsers, ContactContacts, ViewingPropertyViewings (if it wraps a showing), CreatedByUsers.

Inputs: id (required), include.

qobrix_get_offerA

Get a single offer by UUID — offer detail view in the Transaction Lifecycle. Returns { data: { id, ... } }. Include: OpportunityOpportunities (the lead/buyer intent), PropertyProperties (the listing), CreatedByUsers, ModifiedByUsers. Use to trace the deal chain: Offer → Opportunity → Contact + Property → Contract.

Inputs: id (required), include.

qobrix_get_opportunityA

Get a single lead/opportunity by UUID — drill into a pipeline deal in the Sales Pipeline workflow. Returns { data: { id, ref, status, enquiry_type, contact_name, agent, ... } }. RESO mapping: status 'new' = Lead, 'open' = Active/SQL, 'won' = Closed Won, 'closed_lost' = Lost. Verified include: ContactNameContacts (the person), Properties (linked listings), AgentAgents (assigned broker, RESO ListAgent), PropertyTypes, ClosedLostReason, User, OwnerUsers. Canonical deal drill-down pattern: 1. Get the lead with include=['ContactNameContacts','Properties','AgentAgents'] 2. Check showing history: qobrix_list_viewings (search by related property) 3. Check offers: qobrix_search_offers for this lead 4. Check activity: search Tasks, Calls, Meetings linked to this opportunity FK: contact_name → Contacts, agent → Agents, owner → Users.

Inputs: id (required), include.

qobrix_get_projectA

Get a single project by UUID — development detail view for off-plan/new-build listings. Returns { data: { id, ... } }. Verified include: Agents, Developer, Translations, LocationLocations, Assignee, Recommended, Favorites. To see project units: qobrix_search_properties with project == ''. To see project media: qobrix_list_media with related_model='Projects' and related_id=.

Inputs: id (required), include.

qobrix_get_project_coordinatesA

Get lat/lng coordinates for projects, useful for map display. Optionally filter with a search expression.

Inputs: search.

qobrix_get_properties_by_leadA

Get properties explicitly linked to a lead — the 'demand-side view' in the Sales Pipeline. Shows which properties a lead/opportunity has been matched to or expressed interest in. The id parameter is the Opportunity UUID (not the property UUID). In the canonical buyer journey: after Qualification, use this to review Solution/Viewing candidates. Alternative: qobrix_get_opportunity with include=['Properties'] for inline expansion. Complement with qobrix_get_leads_by_property for the reverse (supply-side) match.

Inputs: id (required), page, limit.

qobrix_get_propertyA

Get a single property by UUID — the listing detail view in the Listing Lifecycle. Returns { data: { id, name, status, ... } }. RESO StandardStatus: 'available' = Active, 'reserved' = Pending, 'sold' = Closed, 'withdrawn' = Withdrawn. Use include[] to expand related entities inline: Verified: Agents, PropertyTypes, PropertySubtypes, PropertyViewings, Opportunities, Translations, SalespersonUsers, CreatedByUsers, LocationLocations, AgentAgents, SellerContacts, ProjectProjects. Workflow tips: include=['PropertyViewings'] → see showing history (Showing Lifecycle). include=['Opportunities'] → see interested leads (Sales Pipeline). include=['AgentAgents'] → listing agent (RESO ListAgent). include=['SellerContacts'] → seller/owner (RESO OwnerMember). FK fields: agent → Agents, seller → Contacts, project → Projects, salesperson → Users.

Inputs: id (required), expand, include.

qobrix_get_property_coordinatesA

Get lat/lng coordinates for properties, useful for map display. Optionally filter with a search expression to get coordinates for a subset. Returns array of { id, coordinates } objects.

Inputs: search.

qobrix_get_schemaA

Get the full field schema for any CRM resource — the Schema Discovery tool. CALL THIS FIRST when unsure about field names for search, fields[], or sort. Returns every field with name, type, label, and validation rules (RESO Data Dictionary equivalent). Resources (case-sensitive): Properties, Contacts, Opportunities, Agents, Tasks, PropertyViewings, Projects, Offers, Contracts, Calls, Meetings, EmailMessages, Media, PropertyTypes, PropertySubtypes, PropertyFeatures, Locations. Canonical use: before building search expressions for any workflow (Listing, Sales Pipeline, Follow-up), call this to discover the correct field names. The schema is the source of truth. Pair with qobrix_get_field_options to see allowed enum values for dropdown fields. For search grammar, prefer qobrix_search_dsl_help.

Inputs: resource (required).

qobrix_get_taskA

Get a single task by UUID — task detail for pipeline management and follow-up audit. Returns { data: { id, ... } }. Include: AssignedToUsers (who), ContactContacts (for whom), Properties (which listing), RelatedOpportunityOpportunities (which deal), RelatedAgentAgents, TaskStatus, TaskTypes.

Inputs: id (required), include.

qobrix_get_viewingA

Get a single viewing by UUID — the showing detail view in the Showing Lifecycle. Returns { data: { id, ... } }. Verified include: PropertyViewingViewing. After retrieving, use the property and contact FKs to trace back to the listing and lead.

Inputs: id (required), include.

qobrix_list_agentsB

List agents (RESO Member resource) — real estate brokers managing listings and leads. Agents are the central actor across all workflows: they manage the Listing Lifecycle, drive the Sales Pipeline, and own Follow-up activities. Referenced by property.agent and opportunity.agent FKs. Returns { data: [...], pagination: { count, current_page, has_next_page, ... } }. Verified include: PrimaryContactContacts, User, Brands, AgencyAgents (parent agency). Workflow: to see an agent's full portfolio, search Properties and Opportunities by agent == ''.

Inputs: page, sort, limit, fields, +2 more.

qobrix_list_callsA

List call records — part of the Activity Tracking / Follow-up workflow. Calls are touchpoints in the Lead-Contact Lifecycle; zero tolerance on missed follow-ups. Returns { data: [...], pagination: { count, current_page, has_next_page, ... } }. Include: ContactContacts (who was called), AssignedToUsers (who made the call), RelatedOpportunityOpportunities (which lead), RelatedAgentAgents, RelatedContractContracts. Workflow: use with qobrix_list_meetings and qobrix_list_email_messages for a complete activity timeline. To audit a contact's engagement: search calls, meetings, and emails by their contact UUID.

Inputs: page, sort, limit, search, +1 more.

qobrix_list_contactsA

List contacts (RESO Contacts resource) from Qobrix CRM — the people/companies in the CRM. Core to the Lead-Contact Lifecycle: a Contact becomes a Lead when an Opportunity is created for them. RESO ContactType mapping: use opportunity.enquiry_type and property.seller FK to determine Buyer/Seller/Tenant role. Returns { data: [...], pagination: { count, current_page, has_next_page, ... } }. Key fields (42 total): first_name, last_name, name, email, phone, city, country, assigned_to (UUID → user, RESO OwnerMember), role, is_company, nationality, preferred_language, preferred_contact_method, consent fields, created, modified. Cross-references: property.seller → Contact UUID, opportunity.contact_name → Contact UUID, task.contact → Contact UUID, call/meeting.contact → Contact UUID. Workflow: to see a contact's full journey, search Opportunities, Tasks, Calls, and Meetings by their UUID.

Inputs: page, sort, limit, fields, +3 more.

qobrix_list_contractsA

List contracts (RESO TransactionManagement close) — the 'Deal Signing / Payment / Close' stage of the Sales Pipeline. A Contract finalizes the transaction: it links the Property, buyer/seller Contacts, and the Opportunity. Returns { data: [...], pagination: { count, current_page, has_next_page, ... } }. Verified include: Contacts, PropertyIdProperties, OpportunityIdOpportunities, PaymentInstallments (payment schedule), ContractParties (all signatories), CreatedByUsers. Canonical chain: Offer → Contract → Payment → Close. After contract signing, the property status moves to 'reserved' (Pending), then 'sold' (Closed).

Inputs: page, sort, limit, search, +1 more.

qobrix_list_email_messagesA

List email messages — part of the Activity Tracking / Follow-up workflow and marketing campaigns. Emails are touchpoints in the Lead-Contact Lifecycle for nurturing and communication. Returns { data: [...], pagination: { count, current_page, has_next_page, ... } }. Include: ContactContacts (recipient/sender), Properties (listing references), Campaign (marketing campaign), RelatedOpportunityOpportunities, RelatedAgentAgents. Use with qobrix_list_calls and qobrix_list_meetings for a complete contact engagement timeline.

Inputs: page, sort, limit, search, +1 more.

qobrix_list_mediaA

List media items (RESO Media resource) — photos, documents, floor plans attached to any entity. Core to the Media Lifecycle: every listing needs photos and floor plans; projects need brochures. NOTE: Returns { data: [...] } WITHOUT pagination metadata (unlike other endpoints). Must provide BOTH related_model AND related_id together, or omit both for global media. Listing media: related_model='Properties', related_id=''. Contact documents: related_model='Contacts', related_id=''. Project brochures: related_model='Projects', related_id=''. Alternative: qobrix_list_properties with media=true (default) embeds media in property responses. Each item has: id, media_type, reference_id, display_order, category, created.

Inputs: page, limit, related_id, related_model.

qobrix_list_meetingsA

List meetings — bridges the Activity Tracking and Showing Lifecycle workflows. Meetings can wrap property viewings (RESO Showing ↔ Meeting). Returns { data: [...], pagination: { count, current_page, has_next_page, ... } }. Include: ContactContacts, ViewingPropertyViewings (linked showings), AssignedToUsers, RelatedOpportunityOpportunities, RelatedAgentAgents, CreatedByUsers, ModifiedByUsers. Workflow: include=['ViewingPropertyViewings'] to see which meetings are property showings vs general meetings. The first meeting/showing is the canonical trigger that moves a lead from follow-up to active sales.

Inputs: page, sort, limit, search, +1 more.

qobrix_list_offersA

List offers (RESO TransactionManagement) — the 'Decision Making' stage of the Sales Pipeline. An Offer formalizes a buyer's intent: it links an Opportunity (the lead) to a Property (the listing) with a price. Returns { data: [...], pagination: { count, current_page, has_next_page, ... } }. Include: OpportunityOpportunities (the lead), PropertyProperties (the listing), CreatedByUsers, ModifiedByUsers. Canonical transaction chain: Lead → Viewing → Offer → Contract → Close. After an offer is accepted, the next step is Contract creation (qobrix_list_contracts).

Inputs: page, sort, limit, search, +1 more.

qobrix_list_opportunitiesA

List opportunities/leads from Qobrix CRM — the central entity of the Sales Pipeline and Lead-Contact Lifecycle. In Qobrix, leads ARE opportunities. Each represents a buyer/renter/investor enquiry. RESO funnel mapping: 'new' = MQL/Lead, 'open' = SQL/Active, 'won' = Closed Won, 'closed_lost' = Lost. Returns { data: [...], pagination: { count, current_page, has_next_page, ... } }. IMPORTANT: Avoid include=['Locations']. Safe verified includes: ContactNameContacts, Properties, AgentAgents, PropertyTypes, ClosedLostReason, User, OwnerUsers. Key fields (77 total): status, enquiry_type (buy/rent/invest), buy_rent, contact_name (UUID → Contact), agent (UUID → Agent), owner (UUID → user), source, area_of_interest, bedrooms_from/to, price ranges, next_follow_up_date, enquiry_date. Workflow patterns: Pipeline overview: group by status to see funnel distribution. Overdue follow-ups: status == "open" and next_follow_up_date <= NOW. New leads this week: created >= THIS_WEEK and status == "new". Two-way matching: demand→supply via qobrix_search_properties with lead criteria; supply→demand via this tool with boost against a listing (or native qobrix_get_leads_by_property).

Inputs: page, sort, limit, fields, +2 more.

qobrix_list_projectsA

List projects (property developments/complexes) — RESO Project equivalent for off-plan and new-build listings. Projects group multiple property units under one development, relevant in the Listing Lifecycle for new-builds. Returns { data: [...], pagination: { count, current_page, has_next_page, ... } }. Properties reference their project via the 'project' FK. Verified include: Agents, Developer, Translations, LocationLocations, Assignee, Recommended, Favorites. Workflow: to see units in a project, qobrix_search_properties with project == ''.

Inputs: page, sort, limit, fields, +2 more.

qobrix_list_propertiesA

List properties (RESO Property resource) from Qobrix CRM. Core tool for the Listing Lifecycle — use to browse active inventory, track status changes, or audit listings. Returns { data: [...], pagination: { count, current_page, has_next_page, has_prev_page, page_count, limit } }. RESO StandardStatus mapping: 'available' = Active, 'reserved' = Pending/Under Contract, 'sold' = Closed, 'withdrawn' = Withdrawn. Key fields (184 total): name, ref, status, sale_rent (for_sale/for_rent), property_type, property_subtype, city, country, list_selling_price_amount, list_rental_price_amount, bedrooms, bathrooms, covered_area_amount, plot_area_amount, agent (UUID → qobrix_get_agent), seller (UUID → qobrix_get_contact), project (UUID → qobrix_get_project), salesperson (UUID → user). Workflow recipes: Active inventory → search: status == "available" and sale_rent == "for_sale". Recent listings → sort: '-created'. Buyer-property match → prefer qobrix_search_properties with search + boost for free-language demand. Listing media → follow up with qobrix_list_media(related_model='Properties'). PAYLOAD DEFAULTS: expand=false and media=false — FKs come back as UUIDs and media is not inlined (set expand=true / media=true explicitly when full nested objects or media URLs are needed). Prefer include[] for surgical expansion of specific associations. Default limit 10, max 100.

Inputs: page, sort, limit, media, +4 more.

qobrix_list_tasksA

List tasks — the operational backbone of all canonical workflows: Follow-up, Pipeline Management, and Listing Checklists. Tasks drive daily agent cadence: overdue follow-ups, listing prep, closing steps, payment tracking. Returns { data: [...], pagination: { count, current_page, has_next_page, ... } }. Include: AssignedToUsers (task owner), ContactContacts (related person), Properties (listing), RelatedOpportunityOpportunities (lead), RelatedAgentAgents, RelatedContractContracts, TaskStatus, TaskTypes, CreatedByUsers, ModifiedByUsers. Canonical patterns: My open tasks: search with assigned_to == CURRENT_USER and status != "completed". Overdue: search with due_date <= NOW and status == "pending". Tasks for a lead: search with related opportunity FK. Listing checklist: search tasks linked to a property UUID.

Inputs: page, sort, limit, search, +1 more.

qobrix_list_viewingsA

List property viewings (RESO ShowingAppointment) — the Showing/Viewing Lifecycle tool. Viewings are the pivot between Follow-up and Active Sales: the first viewing marks a lead as actively engaged. Returns { data: [...], pagination: { count, current_page, has_next_page, ... } }. Each viewing links a property, a contact, and an agent/creator. Verified include: PropertyViewingViewing. Workflow patterns: This week's viewings: search with created >= THIS_WEEK. Property's showing history: qobrix_get_property with include=['PropertyViewings']. Meetings linked to viewings: qobrix_list_meetings with include=['ViewingPropertyViewings']. After a showing, the next pipeline step is Offer → Contract → Close.

Inputs: page, sort, limit, search, +1 more.

qobrix_rep_scorecardA

Per-rep productivity roll-up. One call returns a wide row for a single user or a top-N leaderboard (default). Counts calls/meetings/emails/tasks/viewings/opportunities and deals closed; sums total_volume + total_commission from closed contracts. Bucketed by assigned_to across every resource. Example uses: Leaderboard 2026 by volume: { year: 2026 }. My scorecard last 30 days: { user: 'CURRENT_USER', since_days: 30 }. Top 5 by activities this quarter: { from: '2026-01-01', to: '2026-04-01', sort_by: 'activities', top: 5 }.

Inputs: to, top, from, user, +3 more.

qobrix_search_agentsA

Relevance-ranked agent search (F1-optimized). TWO-TIER: search = hard must-haves; boost[] = soft weighted preferences; limit/max_scan control top-N and pool size. With boost: _relevance + _matched; pagination.mode='ranked'. Call qobrix_search_dsl_help({resource:'Agents'}) for DSL + fields. Example: boost=[{field:'ref',op:'contains',value:'LIM',weight:2}], limit=10, max_scan=100.

Inputs: page, sort, boost, limit, +3 more.

qobrix_search_changesA

Search tenant-wide audit log for a CRM resource (Qobrix LogAudit). Calls GET /api/v2/{resource}/changes. Use to find who changed what across all records. Search examples: timestamp >= DAYS_AGO(30), user_id == "", primary_key == "". Pair with qobrix_top_field_changers for leaderboards or qobrix_field_change_history for one record.

Inputs: resource (required), page, sort, limit, +2 more.

qobrix_search_contactsA

Relevance-ranked contact search (F1-optimized). TWO-TIER: search = hard must-haves; boost[] = soft weighted preferences scored over up to max_scan candidates; limit = top-N returned. With boost: rows get _relevance + _matched; pagination.mode='ranked'. Call qobrix_search_dsl_help({resource:'Contacts'}) for DSL + fields. Examples: search='country == "CY"', boost=[{field:'city',op:'==',value:'Limassol',weight:2},{field:'is_company',op:'==',value:false,weight:1}], limit=10, max_scan=100. Hard-only: assigned_to == CURRENT_USER; name contains "Smith"; created >= THIS_MONTH.

Inputs: page, sort, boost, limit, +3 more.

qobrix_search_contractsA

Relevance-ranked contract search (F1-optimized). TWO-TIER: search = hard must-haves; boost[] = soft weighted preferences; limit/max_scan for top-N and pool. With boost: _relevance + _matched; pagination.mode='ranked'. Call qobrix_search_dsl_help({resource:'Contracts'}) for fields. Example: search='contract_type == "cos" and contract_status == "agreed"', boost=[{field:'final_selling_price_amount',op:'>=',value:300000,weight:2}], limit=10, max_scan=150. Prefer qobrix_deals for common closed-deal questions.

Inputs: page, sort, boost, limit, +3 more.

qobrix_search_dsl_helpA

Return the full Qobrix Search Expression DSL reference so you can build correct search strings and boost[] clauses for ANY qobrix_search_* tool (properties, projects, contacts, agents, opportunities, viewings, tasks, offers, contracts). Includes operators, functions, date helpers, association paths, and the two-tier relevance recipe (hard search filter + soft boost ranking + limit/max_scan). Optional resource (e.g. 'Properties', 'Opportunities', 'Contacts') appends a field cheatsheet and live schema field names (schema fetch is response-cached). Call before composing free-language queries.

Inputs: resource.

qobrix_search_offersA

Relevance-ranked offer search (F1-optimized). TWO-TIER: search = hard must-haves; boost[] = soft weighted preferences; limit/max_scan for top-N and pool. With boost: _relevance + _matched; pagination.mode='ranked'. Call qobrix_search_dsl_help({resource:'Offers'}) for fields. Example: search='created >= THIS_MONTH', boost=[{field:'created',op:'>=',value:'2026-01-01',weight:1}], limit=10, max_scan=100.

Inputs: page, sort, boost, limit, +3 more.

qobrix_search_opportunitiesA

Relevance-ranked lead/opportunity search (F1-optimized) — primary pipeline filter AND supply→demand matching. TWO-TIER: search = hard must-haves; boost[] = soft weighted preferences scored over max_scan candidates; limit = top-N. With boost: _relevance + _matched; pagination.mode='ranked'. Call qobrix_search_dsl_help({resource:'Opportunities'}) for DSL + buyer-criteria fields. Pipeline recipes: status == "open"; created >= THIS_WEEK and status == "new"; next_follow_up_date <= NOW and status == "open". Supply→demand (who wants this listing?): search='status in ["new","open"] and buy_rent == "buy"', boost=[{field:'area_of_interest',op:'contains',value:'Limassol',weight:3},{field:'bedrooms_from',op:'<=',value:3,weight:2},{field:'list_selling_price_to',op:'>=',value:400000,weight:2}], limit=15, max_scan=200. Works for projects too (native by-property does not).

Inputs: page, sort, boost, limit, +3 more.

qobrix_search_projectsA

Highly relevant project (development) search for free-language demand (F1-optimized). TWO-TIER RECIPE: (1) search = hard DSL must-haves (server filter → precision). (2) boost[] = soft weighted nice-to-haves scored over up to max_scan candidates (recall + ranking). (3) limit = how many ranked rows to return (default 10, max 100). With boost: top-N with _relevance and _matched; pagination.mode='ranked'. Without boost: fast path single cached page; pagination.mode='fast'. Call qobrix_search_dsl_help({resource:'Projects'}) for DSL + field cheatsheet. Example: search='city contains "Paphos"', boost=[{field:'starting_price_from',op:'<=',value:400000,weight:2},{field:'construction_stage',op:'==',value:'under_construction',weight:1}], limit=10, max_scan=150. When expand=true or media=true, max_scan is auto-capped at 100. If status='result_too_large' with _refine_required, ask the user to narrow then retry. All upstream pages are response-cached.

Inputs: page, sort, boost, limit, +5 more.

qobrix_search_propertiesA

Highly relevant property search for free-language buyer demand (F1-optimized). TWO-TIER RECIPE: (1) search = hard DSL must-haves (server filter → precision). (2) boost[] = soft weighted nice-to-haves scored client-side over up to max_scan candidates (recall + ranking → better precision@top). (3) limit = how many ranked rows to return (default 10, max 100) — raise for more options, keep modest to avoid context overload. With boost: returns top-N with _relevance (score) and _matched (which boosts hit); pagination.mode='ranked' and pagination.scanned shows pool size. Without boost: fast path — single cached list page (pagination.mode='fast'). Call qobrix_search_dsl_help({resource:'Properties'}) before composing queries. Examples: Hard only: search='status == "available" and sale_rent == "for_sale" and city contains "Limassol"'. Demand match: search='status == "available" and sale_rent == "for_sale"', boost=[{field:'sea_view',op:'==',value:true,weight:3},{field:'bedrooms',op:'>=',value:3,weight:2},{field:'list_selling_price_amount',op:'in',value:'200000..600000',weight:2}], limit=15, max_scan=200. PAYLOAD: keep expand=false / media=false for search; when either is true, max_scan is auto-capped at 100. If a result returns status='result_too_large' with _refine_required, ask the user to narrow the query (filters, fields[], smaller limit, drop expand/media) then retry. All upstream pages are response-cached (QOBRIX_CACHE_TTL, default 300s).

Inputs: page, sort, boost, limit, +5 more.

qobrix_search_tasksA

Relevance-ranked task search (F1-optimized) — daily ops for pipeline and follow-up. TWO-TIER: search = hard must-haves; boost[] = soft weighted preferences; limit/max_scan for top-N and pool. With boost: _relevance + _matched; pagination.mode='ranked'. Call qobrix_search_dsl_help({resource:'Tasks'}) for fields. Recipes: search='assigned_to == CURRENT_USER and status != "completed"', boost=[{field:'due_date',op:'<=',value:'2026-12-31',weight:3}], limit=15, max_scan=100. Overdue hard filter: due_date <= NOW and status == "pending".

Inputs: page, sort, boost, limit, +3 more.

qobrix_search_viewingsA

Relevance-ranked viewing search (F1-optimized). TWO-TIER: search = hard must-haves; boost[] = soft weighted preferences; limit/max_scan for top-N and pool. With boost: _relevance + _matched; pagination.mode='ranked'. Call qobrix_search_dsl_help({resource:'PropertyViewings'}) for fields. Example: search='created >= THIS_WEEK', boost=[{field:'created',op:'>=',value:'2026-01-01',weight:1}], limit=10.

Inputs: page, sort, boost, limit, +3 more.

qobrix_sign_inA

Start interactive Qobrix sign-in (Mode C only). When not connected, returns a Sign In to Qobrix link (or native URL elicitation) for the user to complete login + 2FA + consent. When already connected, reports the current identity. In Mode A/B this is a no-op — credentials come from env / request headers. Mode C uses a per-user encrypted session vault keyed by the chat identity (X-Chat-Platform / X-Chat-User-Id). Deliver the Sign In link only to that individual — never post it into a shared/group thread.

Inputs: no named parameters.

qobrix_sign_outA

Sign out of Qobrix (Mode C only). Fully revokes the current user's session: calls the Authorization Server /disconnect (deletes the minted Qobrix API key and clears AS tokens/vault), then clears this user's local encrypted session vault. Other users' vaults on this MCP process are not affected. In Mode A/B there is no interactive session to clear.

Inputs: no named parameters.

qobrix_stale_leadsA

Find live opportunities (default status in [new,open]) that have no recent activity. An opportunity is 'stale' when no call/meeting/email/task touched it within since_days AND the opportunity itself wasn't modified within that window. Sorted oldest-modified first. Example uses: Silent leads (default, 30d): {}. Aggressive cadence: { since_days: 7 }. My silent leads: { assigned_to: 'CURRENT_USER' }.

Inputs: top, statuses, since_days, assigned_to.

qobrix_timeseriesA

Time-bucketed metric (count / sum / avg / min / max) over any resource. Paginates once over the window, buckets in-process by day/week/month/quarter/year (default 'month'). Per-resource default date_field: contracts→date_of_contract, opportunities/properties/calls/etc.→created. Use search to filter (e.g. only closed sales). Set compare_to_prior=true to also fetch the prior identical-length window for YoY %. Example uses: Monthly 2026 closed-sale volume with YoY: resource='contracts', metric='sum', field='final_selling_price_amount', year=2026, search='contract_type == "cos" and contract_status == "agreed"', compare_to_prior=true. Weekly lead intake last 90 days: resource='opportunities', bucket='week', metric='count', since_days=90. Quarterly listing additions 2026: resource='properties', bucket='quarter', year=2026, metric='count'.

Inputs: resource (required), to, from, year, +7 more.

qobrix_top_field_changersA

Leaderboard: which users most often edited a given field (tenant-wide audit scan). Paginates GET /{resource}/changes, counts rows where field appears in changed, groups by user_id. Returns [{user_id, user_name?, change_count, latest_change_at, sample_record_ids}]. Use for: 'who rewrites lead source to direct most often in the last 6 months?'

Inputs: field (required), resource (required), limit, since, +3 more.

qobrix_top_recordsA

Top-N rows of any resource sorted by a numeric or ISO-date field. Paginates server-side (cap 20,000 rows), sorts in-process, and resolves common FK fields (property_id, agent, owner, assigned_to, commission_to, commission_to_2, contact_name, salesperson, seller, project, developer_id, campaign_id) into readable names so the agent doesn't have to chain lookups. Use this for full-dataset top-N, or when list/search sort returns no rows on a nullable/computed field (e.g. opportunities.budget). For a single page of highest list prices, prefer qobrix_list_properties / qobrix_search_properties with sort='-list_selling_price_amount'. Example uses: Top 2026 closed sales: resource='contracts', sort_by='final_selling_price_amount', search='contract_type == "cos" and contract_status == "agreed" and date_of_contract >= "2026-01-01" and date_of_contract < "2027-01-01"'. Largest active listings (full scan): resource='properties', sort_by='list_selling_price_amount', search='status == "available" and sale_rent == "for_sale"'. Most recently modified leads: resource='opportunities', sort_by='modified'.

Inputs: sort_by (required), resource (required), top, desc, +4 more.

qobrix_top_valuesA

Aggregate records by a field and return the top N most-frequent values with counts. This tool paginates through all matching records server-side — no client-side scripting needed. Caps at 20,000 records for safety; use a search filter to narrow large datasets. Example uses: Top listing developers: resource='properties', field='developer_id', search='status == "available" and sale_rent == "for_sale"', resolve=true. Lead sources: resource='opportunities', field='source', search='created >= "2026-01-01"'. Agent workload: resource='opportunities', field='owner', search='status == "open"', resolve=true. Property type mix: resource='properties', field='property_type', search='status == "available"'. Set resolve=true to convert UUIDs (developer_id, agent, owner, etc.) to human-readable names.

Inputs: field (required), resource (required), top, search, +1 more.

qobrix_whoamiA

Return the current Qobrix user profile, capabilities, and portals (GET /api/v2/session/). In Mode C with no session for this chat identity, surfaces a Sign In link so the user can authenticate first. Also includes the OAuth subject when available. Use to confirm which CRM identity the agent is acting as for this user.

Inputs: no named parameters.

qobrix_win_lossA

Win-rate analytics over opportunities. Globally returns counts of new / open / won / closed_lost plus win_rate_pct = won / (won + closed_lost). Optional group_by slices by source, enquiry_type, owner, agent, or closed_lost_reason_id (multi-dim arrays supported). Window applied to last_status_change (fallback modified). Set include_top_losses=true to also receive the 10 most-recent closed_lost opportunities with reason labels and details. Example uses: Overall 2026 win rate: { year: 2026 }. Win rate by lead source: { year: 2026, group_by: 'source' }. Top loss reasons last 90 days: { since_days: 90, group_by: 'closed_lost_reason_id' }. My win rate: { assigned_to: 'CURRENT_USER', year: 2026 }. Forensic loss review: { year: 2026, include_top_losses: true }.

Inputs: to, top, from, year, +5 more.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A4/5.0

    Scored across 64 tools

    Disambiguation4/5

    Most tools are clearly distinct by resource (contacts, properties, opportunities, etc.) and action (list, get, search). However, there are some overlaps: qobrix_get_properties_by_lead and qobrix_get_lead_properties appear to do the same thing (both return properties for a lead), and qobrix_search_offers / qobrix_search_contracts vs qobrix_deals may be confused for deal queries. Also, qobrix_count, qobrix_top_values, qobrix_top_records, qobrix_aggregate, qobrix_timeseries all perform analytics over resources, requiring agents to read carefully to choose the right one.

    Naming Consistency5/5

    Tool names follow a consistent verb_noun pattern: list_<resource>, get_<resource>, search_<resource>, plus a few domain-specific verbs like count, aggregate, timeseries, funnel, win_loss, etc. All are snake_case with clear resource identifiers. The few exceptions (qobrix_deals, qobrix_funnel, qobrix_win_loss) are still consistent with the verb-based style (e.g., deals is a verb-like noun). Overall, the naming is predictable and uniform.

    Tool Count3/5

    64 tools is on the heavier side, but the server covers a full CRM domain with core entities (contacts, properties, opportunities, viewings, tasks, media, projects, offers, contracts, calls, meetings, emails) plus analytics and admin tools. While a 64-tool surface may be overwhelming for agents, the tools are organized and each serves a specific purpose. The count is borderline high but justified by the breadth of functionality. It is not an extreme mismatch (like 100+ or a single trivial tool).

    Completeness4/5

    The tool surface covers the primary CRM lifecycle: list/get/search for all main entities, plus creation/update/delete appears missing for most resources—only search/list/get are provided. However, the server is likely read-only (no create/update/delete tools, except for auth). Given that the domain is CRM data retrieval and analytics, the coverage is quite complete for read operations: all key resources are represented, and advanced analytics (funnel, win_loss, stale_leads, cohort) fill gaps. Some missing operations like creating a contact or updating an opportunity are absent, but the server appears designed as a read/analytics layer rather than a full CRUD tool, so this is a minor gap.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      A comprehensive Model Context Protocol server for real estate data management that provides tools and resources for property listings, agent management, market analysis, client relationships, and area intelligence.
      71
      AGPL 3.0
    • A
      license
      A
      quality
      D
      maintenance
      Read-only MCP server for the Daktela contact center REST API, providing 40 tools to access tickets, calls, emails, chats, contacts, CRM records, campaigns, and real-time agent status.
      45
      1
      MIT
    • A
      license
      C
      quality
      D
      maintenance
      Enterprise-level MCP server integrating with Vista CRM (Loft Edition) for real estate operations, offering 40+ tools for property search, pipeline management, lead capture, and agenda control.
      42
      MIT
    • F
      license
      Not graded
      quality
      C
      maintenance
      MCP server for Moody's Commercial Real Estate API, providing 37 tools for property lookups, market analytics, comps, CMBS data, tax records, and more.
      -