Connection

Endpoint
https://www.matchoffice.com/api/mcp — the only endpoint this server supports. It is not available on any country-specific domain (e.g. matchoffice.dk, matchoffice.se); a call made there returns an error instead of running.
Protocol
MCP JSON-RPC 2.0 over HTTPS POST
Authentication
None required
Transport
Stateless HTTP (StreamableHTTP)

Claude Desktop

Add the server to your Claude Desktop configuration file and restart.

Open the config file for your OS:

macOS   ~/Library/Application Support/Claude/claude_desktop_config.json

Windows   %APPDATA%\Claude\claude_desktop_config.json

Add the lokalebasen entry inside mcpServers:

claude_desktop_config.json
{
  "mcpServers": {
    "lokalebasen": {
      "url": "https://www.matchoffice.com/api/mcp"
    }
  }
}

Restart Claude Desktop. The Lokalebasen tool set appears automatically in the tool picker.


Custom agents & OpenAI Agents SDK

Any MCP-compatible client can connect. Send a POST request with Content-Type: application/json and Accept: application/json.

Initialize handshake
POST https://www.matchoffice.com/api/mcp
Content-Type: application/json
Accept: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {},
    "clientInfo": { "name": "my-agent", "version": "1.0" }
  }
}

Retrieve the full input schemas for all tools via tools/list:

tools/list
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }

Tools

search_locations read-only Search commercial real estate

Search for available lease or sale listings. Geo queries are resolved via autocomplete — pass a city, district, or neighborhood name in any supported language.

Parameters

Name Type Description
country_alpha2 string required ISO 3166-1 alpha-2 country code, lowercase. E.g. dk, se, no, de.
query string optional City, district, or neighborhood — resolved via autocomplete. E.g. Copenhagen, Østerbro, Stockholm, Berlin. Omit for a national search.
section string optional lease (default) or sale.
kinds string[] optional Property types (OR). Lease: office, warehouse, store, coworking, meeting_room, virtual_office, parking, business_center.
area_min / area_max integer optional Area filter in m² (overlap semantics — a listing offering 50–500 m² appears for area_min: 400 because the ranges intersect). area_min must be ≥ 0 and ≤ area_max.
rent_min / rent_max integer optional Rent range. Combine with currency and rent_format.
currency string optional Currency for rent values. Defaults to country native (DKK for dk, SEK for se, etc.). Pass EUR to filter in euros.
rent_format string optional Lease only. monthly (default) = total per month; yearly = per m²/year.
facilities string[] optional Facilities filter (OR). Common values: elevator, parking, wifi, disabled, access_24_hour, electric_car_charge, shower, gym, canteen.
suitable_for string[] optional Tenant type filter (OR). Values: cafe, showroom, clinic, education, popup_store, hair_salon, high_ceiling_storage, storage_hotel, production, workshop.
energy_rating string[] optional Energy rating filter (OR). Values: A2020, A2015, A2010, A, B, C, D, E, F, G, Exempted.
sort_by string optional Sort in column|direction format. E.g. yearly_rent_per_m2_amount_from|asc, area_from|asc, created_at|desc.
page integer optional Page number starting at 1. Returns 20 results per page.

Example

tools/call — search offices in Copenhagen under 25,000 DKK/month
{
  "jsonrpc": "2.0", "id": 3,
  "method": "tools/call",
  "params": {
    "name": "search_locations",
    "arguments": {
      "country_alpha2": "dk",
      "query":          "Copenhagen",
      "kinds":          ["office"],
      "rent_max":       25000,
      "currency":       "DKK",
      "rent_format":    "monthly",
      "area_min":       50
    }
  }
}
Response shape
{
  "total":       142,
  "page":        1,
  "per_page":    20,
  "total_pages": 8,
  "geo": {                              // present only when `query` was set
    "query":                 "Copenhagen",
    "matched_type":          "district",   // "district" | "province" | "city" | "street"
    "postal_district_count": 1
  },
  "locations": [
    {
      "id":                         48291,
      "uuid":                       "a1b2c3...",
      "title":                      "Lyst kontor i hjertet af København",
      "description":               "Moderne kontorlejemål...",  // truncated at 200 chars
      "kind":                       "office",
      "section":                    "lease",
      "area_from":                  65,
      "area_to":                    130,
      "country_alpha2":             "dk",            // always present; from params for country-specific, from DB per item for global
      "latitude":                   55.6761,
      "longitude":                  12.5683,
      "canonical_url":              "https://www.matchoffice.com/leje/...",
      "postal_name":                "Vesterbro",
      "postal_code":                "1620",
      "address_title":              "Vesterbrogade 1",
      "provider_name":              "Acme Real Estate",
      "yearly_rent_per_m2_from":    1950,            // numeric, native currency, lease only
      "yearly_rent_per_m2_to":      2400,
      "monthly_rent_from":          10563,           // total per month, native currency, lease only
      "monthly_rent_to":            13000,
      "sale_price":                 null,            // numeric, sale only
      "currency":                   "DKK",           // ISO 4217, derived from country_alpha2
      "formatted_rent_price":       "1.950 kr. pr. m²",  // localized display string
      "energy_rating":              "B",
      "secondary_kinds":            [],
      "suitable_for_kinds":         []
    }
  ]
}

Use id from search results to call get_location for photos, facilities, provider contact, and full pricing detail.

geo is only present when query was set. If the query couldn't be resolved to a place — no match found, or the geo lookup was temporarily unavailable — geo is null and a geo_note string explains why; results still fall back to a national search for country_alpha2 rather than returning nothing.

geo_note — no match found
{
  "geo":      null,
  "geo_note": "No geographic match found for 'Kobenhavn' in dk. If 'Kobenhavn' is in a different country, set country_alpha2 to the correct code. Showing all results for dk."
}
geo_note — geo lookup unavailable
{
  "geo":      null,
  "geo_note": "Geographic search temporarily unavailable for 'Copenhagen' in dk — showing national results."
}
geo_note — place resolved, but other filters match nothing there
{
  "total":    0,
  "geo":      { "query": "Aalborg", "matched_type": "district", "postal_district_count": 1 },
  "geo_note": "'Aalborg' resolved, but no listings inside it match the given filters."
}
get_location read-only Full details for a single listing

Returns the complete record for one location: description, all photos, facilities list, and canonical URL.

Parameters

NameTypeDescription
id integer required Location ID from search_locations.

Example

tools/call
{
  "jsonrpc": "2.0", "id": 4,
  "method": "tools/call",
  "params": {
    "name":      "get_location",
    "arguments": { "id": 48291 }
  }
}
Response fields
{
  "id", "slug", "title", "description",
  "section",                    // "lease" or "sale"
  "area_from", "area_to", "kind", "secondary_kinds",
  "latitude", "longitude", "full_address",
  "postal_code", "postal_district_name", "country_alpha2",
  "yearly_rent_per_m2_from",    // numeric, native currency, lease only
  "yearly_rent_per_m2_to",
  "monthly_rent_from",          // total per month, native currency, lease only
  "monthly_rent_to",
  "sale_price",                 // numeric, sale only
  "energy_rating",              // "A2020"…"G", or null if exempted
  "exempted_from_energy_rating", // boolean
  "provider_name",              // landlord company name
  "provider_phone",             // landlord phone number
  "primary_photo_url",          // single hero photo (330×255), or null
  "photos",                     // array of thumbnail URLs (330×255)
  "full_photos",                // array of full-resolution URLs (1920×1080)
  "facilities",                 // array of facility name strings
  "canonical_url"               // public listing URL
}

Returns {"errors": ["Location not found"]} or {"errors": ["Location not available"]} when the location does not exist or is not publicly visible.

create_order writes Submit an inquiry to a provider

Sends a contact inquiry to the property provider. Always confirm with the user before calling this tool.

Confirm before submitting. This action sends a real email to the property provider. Verify name, email, and phone number with the user first.

Parameters

NameTypeDescription
location_id integer required Location ID from search_locations.
name string required Full name of the inquirer.
email string required Email address.
phone string required Phone number (minimum 6 characters).
company string optional Company name.
comment string optional Message to the provider — preferred move-in date, team size, specific requirements, etc.

Example

tools/call
{
  "jsonrpc": "2.0", "id": 5,
  "method": "tools/call",
  "params": {
    "name": "create_order",
    "arguments": {
      "location_id": 48291,
      "name":        "Jana Novak",
      "email":       "jana@example.com",
      "phone":       "+45 12 34 56 78",
      "company":     "Novak Consulting ApS",
      "comment":     "Looking for a 6-month lease from Q3. Team of 8."
    }
  }
}
Success response
{ "success": true, "order_id": 19204 }
Validation error response
{ "success": false, "errors": ["Invalid email address 'not-an-email'. Please provide a valid email so the provider can reply."] }

A duplicate inquiry (same email + location) within 30 minutes is rejected. So is a 6th inquiry from the same email within a rolling 24 hours, and so is any inquiry once the MCP server as a whole has processed 50 orders (across all emails) within a rolling 24 hours — all three return the same {"success":false,"errors":[...]} shape with a message explaining the reason.

phone shorter than 6 characters is rejected at the protocol level (before the tool runs), since it's enforced by minLength in the input schema — the response is a generic tool-call error, not the {"success":false,"errors":[...]} shape above.

Protocol-level rejection (e.g. phone too short)
{
  "content": [{ "type": "text", "text": "Invalid arguments: ..." }],
  "isError": true
}

Notes

Geo resolution

The query parameter resolves against the Elasticsearch autocomplete index. The server accepts city names, district names, and neighborhood names in Danish, Swedish, Norwegian, German, and English transliterations. A city name resolves to the whole city — all of its postal districts — not just a single narrower district that happens to share its name. If no matching geo entity has active lease listings at all, the query returns a national result set rather than zero results.

A resolved place combined with other filters (kinds, facilities, rent_min/rent_max, area_min/area_max, ...) that match nothing inside it is not the same as an unresolved place: the response is total: 0 with geo still populated and a geo_note explaining that the place resolved but no listings there match the given filters — it does not fall back to a national result set. Only relax or remove the other filters, not query, to find results in that case.

Pagination

Results are always capped at 20 per page. Use page to paginate: page = ceil(total / 20) gives the last page. The total field reflects the full result count before pagination.

Currency conversion

Rent filters (rent_min, rent_max) are stored natively in each country's currency. Pass currency: "EUR" (or any ISO 4217 code) and the server converts your values to the country currency before filtering. An unsupported currency code returns an error rather than silently skipping conversion.

currency cannot be combined with rent_min/rent_max when country_alpha2: "global" — a global search spans multiple native currencies with no single target to convert into. Specify a country, or omit currency and the rent bounds for a global search.

Rent format

monthly = total rent per calendar month. yearly = rent per m² per year (typical for Danish commercial leases). Both formats accepted; defaults to monthly. Has no effect for section: "sale".