Qobrix CRM Server
БесплатноНе проверенA read-only MCP server providing 56 tools to query Qobrix real-estate CRM data, covering listings, leads, viewings, offers, contracts, analytics, and more, with
Описание
A read-only MCP server providing 56 tools to query Qobrix real-estate CRM data, covering listings, leads, viewings, offers, contracts, analytics, and more, with RESO Data Dictionary alignment and caching support.
README
Qobrix CRM MCP Server
Connect Claude, Cursor, and other MCP clients to your Qobrix real-estate CRM — listings, leads, viewings, offers, contracts, and activity in one read-only Model Context Protocol layer.
64 tools (CRM entities + AI relevance search + analytics + audit + cache controls + session/identity), RESO Data Dictionary 2.0 workflows, optional Redis-backed response caching, three auth modes (stdio / headers / OAuth 2.1), and 226 automated tests.
GitHub · Qobrix CRM · MCP specification · RESO DD 2.0
Table of contents
- User Guide — Mode A → Mode B → Mode C step-by-step
- What it does
- Who it is for
- Canonical real-estate workflows
- Tools at a glance
- Quick start
- Configuration
- Auth modes
- Enterprise OAuth
- Caching
- Cursor IDE setup
- Other MCP hosts
- Search expression syntax
- Fetching related data
- Testing
- Architecture
- How the LLM learns
- Technology
- License
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 ChatGPT, Claude, 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_valuesfor YoY-style metrics without custom scripts, and response caching to cut API load on repeat queries. - Enterprise IT ready for per-agent identity: run Modes A/B from this package, then pair Mode C 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_sign_in, qobrix_sign_out, 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 | 3 | Interactive sign-in (qobrix_sign_in), full revoke sign-out (qobrix_sign_out), current user profile (qobrix_whoami) — Mode C; sensible no-ops in Modes A/B |
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/sharpsir-group/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 (Mode A) | Qobrix instance base URL |
QOBRIX_API_USER |
Yes (Mode A) | X-Api-User header value (UUID) |
QOBRIX_API_KEY |
Yes (Mode A) | X-Api-Key header value |
QOBRIX_LOCALE |
No | X-Locale header (e.g. en-US, el-GR) |
Auth modes
Clone this package, run Mode A or B, and put live Qobrix data in front of Claude, Cursor, or any MCP client — Apache 2.0.
| Mode | In this package? | When | How credentials arrive |
|---|---|---|---|
| A (default) | Yes | QOBRIX_MCP_TRANSPORT=stdio (or unset) |
Shared QOBRIX_API_* from process env |
| B | Yes | TRANSPORT=http + QOBRIX_MCP_AUTH=headers |
Per-request X-Api-User / X-Api-Key (trusted callers; bind localhost) |
| C | Needs companion AS | TRANSPORT=http + QOBRIX_MCP_AUTH=oauth |
Self-service OAuth: MCP returns a /connect URL; user signs in at SharpSir’s Enterprise OAuth Authorization Server; this server holds the session |
Modes A and B are fully supported out of this package. Mode C is for per-user authenticated CRM access (no northbound client OAuth wiring) and requires SharpSir’s separate Enterprise OAuth / SSO product — not distributed as part of this repo.
Enterprise OAuth
Need the agent to work as a signed-in Qobrix user — not a shared API key? Mode C 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 Mode C works (MCP self-auth — northbound clients unchanged):
- A tool runs with no session → the MCP returns an authorization URL:
- URL-mode elicitation (
JSON-RPC -32042) when the client supportselicitation.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)
- URL-mode elicitation (
- The user opens
/connecton this server (anti-phishing indirection) → signed cookie + redirect to the Enterprise OAuth login page - 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 - The next tool call runs authenticated. On Qobrix
401/403, the vault is cleared and a fresh/connectURL is returned - Agents can also call
qobrix_sign_in,qobrix_whoami, andqobrix_sign_out(full revoke via AS/disconnect+ Qobrix API-key delete)
- 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 — Mode C is hard-wired to this Enterprise OAuth solution only.
- Security: Mode C uses per-user encrypted session vaults (keyed by
chat identity headers) and leaves
/mcpwithout a client bearer. BindQOBRIX_MCP_HOST=127.0.0.1and setQOBRIX_MCP_IDENTITY_SECRET(shared only with the trusted MCP host like ragchat) so identity headers cannot be forged. Keep vault encryption onQOBRIX_MCP_STATE_SECRET(MCP-only). If you reverse-proxy for browsers, publish only/connectand/oauth/callback— deny public/mcpand/health. Local agents (ragchat) callhttp://127.0.0.1:<port>/mcp. WhenALLOWED_HOSTSlists only the public hostname, loopback Host values (127.0.0.1/localhost/::1) are auto-added if the server binds to loopback. Connect cookiePathfollowsPUBLIC_URLpathname; Expresstrust proxyis2behind Cloudflare→Apache. Deliver/connectlinks only to the individual user — never into a shared/group thread.
Ready to upgrade? Contact SharpSir Group · [email protected] 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
Mode C 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 writeGET /health— includesconnectedandsession_vaultscount- Unauthenticated
/mcpis intentional for northbound clients: tools surface the connect URL when needed — keep/mcpon localhost in production
See docs/USER_GUIDE.md for Mode A → B → C step-by-step, reverse-proxy lockdown, and Host allowlist details.
Register the remote MCP URL (…/mcp) in Claude / Cursor / ChatGPT / ragchat as a normal Streamable HTTP server (no client-side OAuth provider required). The MCP handles auth itself.
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()): setQOBRIX_REDIS_URLto 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
This server uses stdio MCP (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.jsmust exist (npm run build) before adding the MCP entry.
2. Credentials
- Copy the template:
cp .env.example .env - Edit
.envand set at leastQOBRIX_API_URL,QOBRIX_API_USER, andQOBRIX_API_KEY(see Configuration). - Keep
.envout 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.
4. Recommended: node --env-file (Node 20+)
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.envis populated even when the host’senvFilefield 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
- Reload MCP — Command Palette → MCP restart, or reload the Cursor window.
- Check logs — View → Output → pick “MCP” / “MCP Logs” in the dropdown; fix path or Node errors there.
- 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 Desktop — 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. Useqobrix_get_field_optionsfor enum values andqobrix_get_schemafor 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:
search— hard must-haves (server-side DSL filter → precision floor).boost[]— soft weighted nice-to-haves scored in-process over a candidate pool (recall + ranking).limit— how many ranked rows to return (default 10, max 100). Raise for more options; keep modest to avoid context overload.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_projectswithsearch+boost. Native:qobrix_get_properties_by_lead/qobrix_get_lead_properties. - Supply → demand:
qobrix_search_opportunitieswith open-leadsearch+boostagainst 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, …).
Fetching Related Data
Three strategies to resolve foreign keys:
include[]parameter — expand associations inline in one call
qobrix_get_property({ id: "...", include: ["Agents", "PropertyViewings"] })
- Separate get call — take the UUID from an FK field and call the appropriate tool
// property.agent → UUID
qobrix_get_agent({ id: "<agent-uuid>" })
- 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 ofdata[]that fits, and a_truncatedblock is attached withkept_rows,omitted_rows,original_chars,max_chars, and ahinttelling 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, overrideQOBRIX_MCP_REFINE_MULTIPLIER): returnsstatus: "result_too_large"with_refine_required(assistant instruction + suggested narrowing + smallreturned_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 aQOBRIX_MCP TRUNCATEDtrailer 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 — Mode B header rejection + Mode C /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 | 3 | Mode B without headers, Mode C /mcp without bearer, elicitation /connect URL |
Architecture
src/
├── index.ts # MCP server entry point + RESO workflow instructions
├── http.ts # Streamable HTTP transport (Modes B / C)
├── modes.ts # Auth mode resolution (env / headers / oauth)
├── client.ts # QobrixClient — HTTP + read-through response cache
├── auth-context.ts # AsyncLocalStorage per-request credentials
├── oauth-client.ts # Mode C 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 # Mode B/C auth smoke tests
How the LLM Learns
The server teaches the LLM at three levels:
Server instructions — top-level
instructionsfield in the MCPinitializeresponse provides the full data model, six canonical workflows with tool recipes, search syntax, FK resolution strategies, and known quirks.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-tiersearch+boostrecipe;qobrix_search_dsl_helpexposes the full DSL on demand.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 (Modes B / C) |
| API Auth | Mode A/B: X-Api-User + X-Api-Key · Mode C: 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
Modes A and B are included in this open-source package. Mode C pairs with SharpSir’s Enterprise OAuth Authorization Server (SSO / per-user identity) — a separate commercial product delivered upon request — sharpsir.group · [email protected].
Part of the Sharp Matrix platform · sharpsir.group
Установить Qobrix CRM Server в Claude Desktop, Claude Code, Cursor
unyly install qobrix-crm-mcp-serverСтавит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.
Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh
Или настроить вручную
Выполни в терминале:
claude mcp add qobrix-crm-mcp-server -- npx -y github:sharpsir-group/qobrix-crm-mcpFAQ
Qobrix CRM Server MCP бесплатный?
Да, Qobrix CRM Server MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Qobrix CRM Server?
Нет, Qobrix CRM Server работает без API-ключей и переменных окружения.
Qobrix CRM Server — hosted или self-hosted?
Доступен hosted-вариант: Unyly запускает сервер в облаке, локальная установка не обязательна.
Как установить Qobrix CRM Server в Claude Desktop, Claude Code или Cursor?
Открой Qobrix CRM Server на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectCompare Qobrix CRM Server with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
