Skip to main content
Glama
srnux

proptech-inquiry

by srnux

proptech-inquiry-agent

An agent that answers and triages inquiries about property listings. It is built on an MCP server, so the same tools work from Claude Desktop, Claude Code, the MCP Inspector or the agent loop in this repo.

All listing data is synthetic.

Status

Slice

What it adds

State

1

MCP server: structured search, listing lookup, hand-off to a human

done

2

Retrieval over listing text and a policy FAQ, exposed as an MCP tool

next

3

Agent loop that answers inquiries using the tools

planned

4

Eval suite: correct answers, correct escalations, no invented facts

planned

5

Architecture write-up

planned

Related MCP server: Repliers MCP Server

The one design rule

The agent answers facts from the record and escalates everything else. Viewings, negotiation, contract and legal questions, complaints, personal data requests and anything the record does not cover go to hand_off_to_human. The boundary sits in the tool contract, not only in a prompt, so it can be tested.

Tools

Tool

Kind

Purpose

search_listings

read

Hard-criteria search. Returns summaries without description text.

get_listing

read

One full record. The model is told to answer only from its fields.

hand_off_to_human

write

Creates a ticket with a fixed reason code. Rejects unknown listing ids.

Worked example

One inquiry that uses all three tools: search on hard criteria, answer a detail from the record, and hand off the part the agent may not handle.

I have a small dog and want a flat in Hamburg under 2,000 € a month. Is heating included, and can I view it on Saturday?

1. Search. The dog and the budget are hard criteria.

search_listings { "city": "Hamburg", "offerType": "rent", "propertyType": "apartment", "maxPrice": 2000, "hasPet": true }

This returns HH-1001 (Eimsbüttel, 1,650 EUR cold, pets on-request). HH-1002 is left out because it allows no pets.

2. Look up the detail. Heating is not a structured field, so the agent opens the full record.

get_listing { "id": "HH-1001" }

The description says utilities are 240 EUR per month with heating included, which makes 1,890 EUR in total. It also says "pets on request, small dogs usually fine".

3. Hand off the viewing. Booking a viewing is on the escalation list, so the agent does not agree to a date.

hand_off_to_human {
  "reason": "viewing_request",
  "listingId": "HH-1001",
  "summary": "Wants a Saturday viewing of HH-1001. Has a small dog; pets are on request, so the landlord needs to confirm.",
  "contactEmail": null
}

Expected answer

  • HH-1001 fits the budget: 1,650 EUR cold plus 240 EUR utilities, 1,890 EUR in total.

  • Heating is included in the utilities.

  • Pets are on request. The listing says small dogs are usually fine, but that is not a yes.

  • The Saturday request has gone to the letting team, and they will confirm the time. The agent asks for an email address if it does not have one.

The agent must not

  • say the dog is allowed,

  • confirm or suggest a viewing slot,

  • quote a heating figure that is not in the record.

Run it

npm install
npm test          # unit tests plus MCP client round-trip tests
npm run inspect   # open the MCP Inspector against the server

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "proptech-inquiry": { "command": "npx", "args": ["tsx", "/path/to/proptech-inquiry-agent/src/mcp/stdio.ts"] }
  }
}

Layout

data/listings.json     synthetic catalogue, validated with zod at load
src/domain/            listing model, repository, hand-off queue (no MCP imports)
src/mcp/server.ts      tool definitions; takes its dependencies as arguments
src/mcp/stdio.ts       stdio entry point
test/                  repository tests and in-memory MCP client tests

License

MIT

Available Tools

3 tools
get_listingGet listingA
Read-only

Return the full record of one listing, including its description text. Answer only from fields present here. If the answer is not in the record, say so and hand off with reason not_answerable_from_listing instead of guessing.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesListing id, e.g. HH-1001

TDQS

A4/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is covered by structured data. The description adds genuinely useful behavioral policy not present in the annotations: answer strictly from returned fields and refuse/redirect rather than guess when data is absent.

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

Conciseness5/5

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

Three short sentences, front-loaded with the purpose and followed by the grounding and fallback rules. Nothing is redundant and every sentence changes agent behavior.

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

Completeness4/5

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

With one required parameter, full schema coverage, and no output schema, the definition covers what an agent needs: what comes back ('full record' incl. description) and how to behave when the record lacks the answer. It could briefly note behavior for a missing/invalid id, which keeps it from a 5.

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

Parameters3/5

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

Schema description coverage is 100% and the single id parameter is documented with an example ('HH-1001'), so the schema carries the full burden. The description adds no syntax, format, or validation detail beyond it, which matches the baseline 3 for a fully documented parameter.

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

Purpose4/5

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

The description names a specific verb and resource ('Return the full record of one listing') plus what the record contains ('including its description text'). It implicitly distinguishes itself from sibling search_listings by scoping to a single record, though it never names the alternative explicitly.

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

Usage Guidelines4/5

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

It gives a clear grounding rule ('Answer only from fields present here') and an explicit when-not path ('If the answer is not in the record... hand off with reason not_answerable_from_listing instead of guessing'), which routes the agent to the hand_off_to_human sibling. It does not state prerequisites for obtaining the listing id, but the context of use is clear.

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

hand_off_to_humanHand off to a human agentA

Create a ticket for the letting or sales team. You MUST use this, and not answer yourself, for: booking viewings, any price or rent negotiation, contract or legal questions, complaints, requests about personal data, and any question the listing record does not answer. Summarise the inquiry in one or two sentences so the human does not have to reread the thread.

ParametersJSON Schema
NameRequiredDescriptionDefault
reasonYes
summaryYes
listingIdYesListing the inquiry is about, or null
contactEmailYes

TDQS

A4.3/5.0
Behavior4/5

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

Annotations already declare this is a non-read-only, non-destructive, non-idempotent, closed-world write, and the description's "Create a ticket" is consistent with that. It adds useful behavioural context about how the summary is consumed ("so the human does not have to reread the thread"), but does not say whether the customer is notified or what happens after the ticket is filed.

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

Conciseness4/5

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

Two sentences, front-loaded with the action and then the escalation trigger list. The long MUST clause is dense but every item earns its place as a routing rule; only the trailing summary instruction could be tightened slightly.

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

Completeness4/5

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

For a 4-required-parameter escalation tool with no output schema, the description covers routing, required intent categories, and summary style. It leaves contactEmail semantics and any post-creation behaviour (ticket id, notification, SLA) unspecified, which is a minor but real gap.

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

Parameters3/5

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

Schema coverage is only 25% (only listingId is documented in the schema). The enumerated use cases in the description map well onto the reason enum values, and the summary wording gives a length/style expectation, but contactEmail (whose address, required even when the listing is unrelated) and listingId's null case are never explained in the description.

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

Purpose5/5

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

The description opens with a specific verb and resource: "Create a ticket for the letting or sales team." That is unambiguous and clearly distinct from the sibling tools search_listings and get_listing, which read listing data rather than escalate an inquiry to a human.

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

Usage Guidelines5/5

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

It gives an explicit MUST-use list (viewing bookings, price/rent negotiation, contract or legal, complaints, personal data requests, and anything the listing record cannot answer) plus the exclusion "not answer yourself," which implicitly routes answerable questions to get_listing. When-to-use and when-not-to-use are both covered.

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

search_listingsSearch listingsA
Read-only

Find listings by hard criteria (city, rent or sale, price ceiling, rooms, area, features, move-in date). Use this when the inquirer states requirements. Returns summaries sorted by price, cheapest first. Do not use it to answer questions about a specific listing's details; call get_listing for that.

ParametersJSON Schema
NameRequiredDescriptionDefault
cityNoCity name, e.g. Hamburg, Berlin, München, Köln
limitNo
hasPetNoTrue if the inquirer has a pet. 'on-request' listings are kept; never promise them.
featuresNoAll must match, e.g. balcony, lift, garden, accessible
maxPriceNoEUR per month for rent, EUR total for sale
minRoomsNo
offerTypeNo
minAreaSqmNo
availableByNoLatest acceptable move-in date, YYYY-MM-DD
propertyTypeNo

TDQS

A4.6/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds return behavior not present in structured data: results are summaries sorted by price, cheapest first. It does not address pagination or how the limit truncates results, so it stops short of full transparency.

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

Conciseness5/5

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

Three tight sentences with zero filler. The filter criteria and the primary usage condition are front-loaded, and the exclusion/alternative follows immediately after.

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

Completeness4/5

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

For a read-only search tool with no output schema, the description supplies the return shape (summaries sorted by price) and the routing rule to get_listing. It omits the limit/default page size and never mentions propertyType, which is a minor but real gap.

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

Parameters4/5

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

Schema coverage is 50%, so several parameters (offerType, minRooms, minAreaSqm, propertyType, limit) lack schema descriptions. The description compensates by naming rent-or-sale, price ceiling, rooms, area and move-in date as filter criteria, covering most undocumented params, though propertyType and limit remain unmentioned anywhere.

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

Purpose5/5

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

The description opens with a specific verb+resource ("Find listings") and enumerates the hard criteria it filters on, so the agent knows exactly what the tool retrieves. It also explicitly contrasts itself with get_listing, making it distinguishable from its sibling without opening either schema.

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

Usage Guidelines5/5

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

It states the triggering condition ("Use this when the inquirer states requirements") and an explicit exclusion with the correct alternative ("Do not use it to answer questions about a specific listing's details; call get_listing for that"). Both when-to-use and when-not-to-use are covered.

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

Tool Schema Changelog

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

  1. 3 tool updatesv0.1.0
    • First observedget_listing
    • First observedhand_off_to_human
    • First observedsearch_listings

TDQS

A4.3/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a distinct action: search by criteria, retrieve one record, or escalate to a human. The descriptions explicitly define boundaries (search_listings says do not use it for a specific listing; get_listing says answer only from the record), so misselection is unlikely.

Naming Consistency5/5

All three names follow a clear verb_noun pattern (search_listings, get_listing, hand_off_to_human) with consistent snake_case.

Tool Count4/5

Three tools is lean for the narrow inquiry-handling scope, and each earns its place, though it sits near the lower bound where coverage could feel thin for edge cases.

Completeness4/5

The read-plus-escalate lifecycle is well covered: criteria search, full record retrieval, and human handoff for bookings, negotiation, legal, and unanswerable questions. Only listing management/creation is absent, which is outside an inquiry agent's stated purpose.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    Enables users to access the Realty In Ca1 API to search for residential and commercial property listings, view detailed property information, and retrieve neighborhood demographics or statistics. It supports filtering by geographic location, price range, building type, and other real estate characteristics.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides AI assistants access to real-time MLS data via the Repliers API, enabling natural language property search, market statistics, and listing details.
    325 npm
    16
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI assistants to operate property management systems via natural language, covering repair orders, owner info, payments, notices, and inspections. Features a full agentic workflow with human-in-the-loop and observability.
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to look up real estate agents, search MLS listings, submit buyer/seller leads, and access proprietary seller-intent signals in SC and GA.
    MIT