Files
fredy/lib/mcp/README.md
2026-04-27 16:56:04 +02:00

14 KiB
Raw Blame History

Fredy MCP Server

The Fredy MCP Server exposes your real estate jobs and listings data to LLM clients. It supports two transports:

  • Stdio: for local LLM clients (Claude Desktop, LM Studio, llm-cli, mcp-cli, etc.)
  • Streamable HTTP: for remote LLM clients (ChatGPT, cloud-hosted agents, etc.)

Authentication

All MCP access is token-based based. Every Fredy user is automatically assigned a permanent, non-expiring MCP token when their account is created. This token is a secret and should be treated like a password.

Where to find your token

MCP tokens are displayed in the User Management list (Admin → Users). Each user's token is shown in the "MCP Token" column.

Important: MCP tokens never expire. They are permanent secrets tied to each user account. If a token is compromised, you must change the token! If you chose to use a token from an admin account, the LLM can query information from ALL jobs/listings.

Available Tools

Tool Description
list_jobs List real estate search jobs with pagination and text filtering
get_job Get detailed information about a specific job
list_listings Search and list real estate listings with pagination, text search, and filters
get_listing Get full details of a single listing
get_current_date_time Gets the current date/time for the llm to be used

Tool Details

list_jobs

  • page (number, optional) Page number (default: 1)
  • pageSize (number, optional) Results per page (default: 50, max: 1000). Use pagination to fetch more.
  • filter (string, optional) Free-text filter on job name

Response: markdown table with columns ID, Name, Enabled, Active Listings. Includes summary and pagination info.

get_job

  • jobId (string, required) The job ID to retrieve

list_listings

  • page (number, optional) Page number (default: 1)
  • pageSize (number, optional) Results per page (default: 50, max: 1000). Use pagination to fetch more.
  • filter (string, optional) Free-text search across title, address, provider, link
  • jobId (string, optional) Filter listings by job ID
  • activeOnly (boolean, optional) When true, only show active listings
  • provider (string, optional) Filter by provider name
  • createdAfter (number, optional) Only include listings created at or after this unix timestamp in milliseconds (e.g. 1772008362564). Useful for queries like "give me all listings from today".
  • createdBefore (number, optional) Only include listings created at or before this unix timestamp in milliseconds (e.g. 1772008362564).
  • minPrice (number, optional) Only include listings with price >= this value (e.g. 500). Numeric, no currency symbol.
  • maxPrice (number, optional) Only include listings with price <= this value (e.g. 1500). Numeric, no currency symbol.
  • sortField (string, optional) Sort by: created_at, price, size, provider, title, is_active
  • sortDir (string, optional) Sort direction: asc or desc

Response: markdown table with columns ID, Title, Address, Price, Size, Provider, Active, Created, Job. Includes summary and pagination info. Use get_listing for full details.

Note: All timestamps are unix timestamps in milliseconds (e.g. 1772008362564), not seconds.

get_listing

  • listingId (string, required) The listing ID to retrieve

Usage with Local LLM (stdio transport)

The stdio transport communicates over stdin/stdout and is ideal for local LLM tools.

Quick Start

MCP_TOKEN=fredy_<your-token> node mcp/stdio.js
# or
MCP_TOKEN=fredy_<your-token> yarn mcp:stdio

Testing with MCP Inspector

The MCP Inspector lets you interactively test your MCP server in a browser UI.

npx @modelcontextprotocol/inspector -e MCP_TOKEN=fredy_<your-token> -- node mcp/stdio.js

Once the inspector is running, open the URL shown in your terminal (usually http://localhost:6274). You can then:

  1. Click Connect to establish the stdio connection
  2. Go to the Tools tab to see all available tools
  3. Select a tool, fill in parameters, and click Run to test it

LM Studio Configuration

LM Studio supports MCP servers natively, allowing your local LLM to access Fredy's jobs and listings data.

Setup

  1. Open LM Studio and load a model that supports tool use (e.g., Qwen 2.5, Llama 3.1, Mistral, etc.)

  2. In the right side under Integrations click on "# install" and "edit mcp.json"

  3. Edit the LM Studio MCP config file directly (~/.lmstudio/config/mcp.json or via the UI export):

    {
      "mcpServers": {
        "fredy": {
          "command": "node",
          "args": ["/absolute/path/to/fredy/mcp/stdio.js"],
          "env": {
            "MCP_TOKEN": "fredy_<your-token>"
          }
        }
      }
    }
    
  4. Toggle the server on: LM Studio will spawn the stdio process and connect

  5. You should see the Fredy tools appear as available tools

Suggestion on LLM

After testing numerous LLM's, I got the best results with Qwen 3.5 or Qwen 2.5.. E.g. Qwen2.5-14B-Instruct-1M-8bit.

Usage

Once connected, simply ask your LLM about your real estate data in natural language:

  • "Show me all my active search jobs"
  • "List the latest listings from my Berlin apartment search"
  • "Get details for listing XYZ"
  • "What are the cheapest listings across all my jobs?"

The LLM will automatically call the appropriate Fredy MCP tools and present the results.

Tip: Make sure Fredy is running and the database is accessible before starting the MCP server in LM Studio. The stdio transport initializes its own database connection, so Fredy's main process does not need to be running, but the database file must exist and be up-to-date (migrations applied).

Claude Desktop Configuration

Claude Desktop supports MCP servers natively via its developer settings.

Setup

  1. Open Claude Desktop

  2. Go to Settings → Developer → Edit Config - this opens the claude_desktop_config.json file

  3. Add the fredy server to the mcpServers object:

    {
      "mcpServers": {
        "fredy": {
          "command": "/opt/homebrew/opt/node@22/bin/node",
          "args": ["/absolute/path/to/fredy/lib/mcp/stdio.js"],
          "env": {
            "MCP_TOKEN": "fredy_<your-token>"
          }
        }
      }
    }
    

    Replace /absolute/path/to/fredy with the actual path on your machine (e.g. /Users/you/dev/fredy).

    Important: Claude Desktop launches with a restricted PATH and often cannot find node by name. Always use the full absolute path to the node binary. Find yours by running which node in a terminal. Common locations:

    • Homebrew (default): /opt/homebrew/bin/node
    • Homebrew (versioned, e.g. node@22): /opt/homebrew/opt/node@22/bin/node
    • nvm: /Users/<you>/.nvm/versions/node/<version>/bin/node
  4. Save the file and restart Claude Desktop

  5. You should see a hammer icon (🔨) in the chat input - click it to confirm the Fredy tools are listed

Usage

Once connected, simply ask Claude about your real estate data:

  • "Show me all my active search jobs"
  • "List the latest listings from my Berlin apartment search"
  • "What are the cheapest apartments added this week?"

Claude will automatically call the appropriate Fredy MCP tools.

Note: Fredy's main web process does not need to be running - the stdio transport opens its own database connection directly. But the SQLite database file must exist and migrations must have been applied.


Usage with Remote LLM (Streamable HTTP transport)

The HTTP transport is automatically available when Fredy is running. It uses the MCP Streamable HTTP protocol at:

POST   /api/mcp    JSON-RPC messages (initialize, tool calls)
GET    /api/mcp    SSE stream for server-initiated notifications
DELETE /api/mcp    Terminate session

Authentication

All requests must include the token as a Bearer token:

Authorization: Bearer fredy_<your-token>

Example: Initialize a session

curl -X POST http://localhost:9998/api/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer fredy_<your-token>" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-03-26",
      "capabilities": {},
      "clientInfo": { "name": "test-client", "version": "1.0.0" }
    }
  }'

Example: Call a tool

curl -X POST http://localhost:9998/api/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer fredy_<your-token>" \
  -H "Mcp-Session-Id: <session-id-from-init-response>" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "list_jobs",
      "arguments": { "page": 1, "pageSize": 10 }
    }
  }'

Security

  • Every user is automatically assigned a permanent MCP token at account creation tokens never expire
  • Tokens are cryptographically random (256-bit) and prefixed with fredy_
  • Each token is scoped to a single user the LLM can only access that user's data
  • Non-admin users only see their own jobs and jobs shared with them
  • Tokens are stored in the mcp_token column of the users table
  • Tokens are deleted automatically when the owning user is removed
  • The /api/mcp endpoint uses Bearer token auth (independent of cookie-session)
  • Treat MCP tokens like passwords do not share them publicly

Response Format

All tool responses use markdown instead of JSON for maximum LLM readability and token efficiency:

  • List responses (list_jobs, list_listings) use markdown tables with a summary line and pagination footer
  • Detail responses (get_job, get_listing) use markdown key-value lists
  • Error responses include the tool name and error message

Example list response:

**Tool:** list_listings | **Status:** OK

Found **85** listing(s). Showing page 1 of 2 (50 on this page). More pages available - use page=2 to continue.

| ID | Title | Address | Price | Size | Provider | Active | Created | Job |
|----|-------|---------|-------|------|----------|--------|---------|-----|
| abc123 | Nice flat | Berlin | 1200 | 70 | immoscout | yes | 2026-02-25 10:30:00 | My Search |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |

Use **get_listing** with an ID for full details (description, link, image).

**Page:** 1/2 | **Has more:** yes

Example detail response:

**Tool:** get_listing | **Status:** OK

### Listing: Nice flat

- **ID:** abc123
- **Title:** Nice flat
- **Address:** Berlin
- **Price:** 1200
- **Size:** 70
- **Provider:** immoscout
- **Link:** https://...
- **Active:** yes
- **Created:** 2026-02-25 10:30:00

Markdown is used because it is significantly more token-efficient than JSON (~40-60% fewer tokens for tabular data) and natively understood by all LLMs.

Architecture

┌─────────────────┐     stdio      ┌──────────────┐
│  Local LLM      │◄──────────────►│  mcp/stdio.js│
│  (LM Studio,    │                │  (transport)  │
│   Claude, etc.) │                │               │
└─────────────────┘                └──────┬───────┘
                                          │
┌─────────────────┐  Streamable HTTP ┌────┴────────┐
│  Remote LLM     │◄───────────────►│  /api/mcp    │
│                 │  (Bearer token) │  (transport)  │
└─────────────────┘                 └──────┬───────┘
                                           │
                                ┌──────────┴──────────┐
                                │  mcpAuthentication  │
                                │  (token validation, │
                                │   access control)   │
                                └──────────┬──────────┘
                                           │
                                  ┌────────┴────────┐
                                  │  mcpAdapter.js  │
                                  │  (tool routing  │
                                  │   + data fetch) │
                                  └────────┬────────┘
                                           │
                                  ┌────────┴────────┐
                                  │ mcpNormalizer.js│
                                  │ (markdown       │
                                  │  formatting)    │
                                  └────────┬────────┘
                                           │
                                    ┌──────┴───────┐
                                    │  Fredy DB    │
                                    │  (SQLite)    │
                                    └──────────────┘