Skip to main content
Glama
README.md
# kleinanzeigen-mcp

> **Unofficial project.** Not affiliated with, endorsed by, or connected to
> Kleinanzeigen. "Kleinanzeigen" is used only to describe what the software
> talks to. This software performs **user-directed** browser automation on
> publicly available listings — no account, no login.

A read-only [Model Context Protocol](https://modelcontextprotocol.io) server for
public [kleinanzeigen.de](https://www.kleinanzeigen.de) listings. It lets an AI
agent search listings and read a single listing through a local browser session.

No AI, no chat UI, no cloud service, no login, no messaging.

## Acceptable use

The tool reads public listings, one request at a time, on behalf of a single
person. It deliberately does **not**:

- log in, create accounts, or touch any account,
- read inboxes, send messages, or manage saved searches,
- solve or bypass CAPTCHAs, challenges or access controls,
- rotate proxies or spoof fingerprints to evade blocks,
- crawl or bulk-export listings.

Every action maps to one user request: one search, one listing. Searches are
capped at 10 result pages. You are responsible for complying with the terms of
service of any site you access through this software.

## Requirements

- Node.js >= 20

## Install

From npm:

```bash
npm install -g @washedguy/kleinanzeigen-mcp
npx patchright install chromium
```

Or from a checkout:

```bash
npm install
npx patchright install chromium
npm run build
```

The browser runtime is [Patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright),
a drop-in Playwright fork that closes Playwright's automation leaks
(`Runtime.enable`, `navigator.webdriver`). If a real Google Chrome is installed it
is used automatically for a more realistic fingerprint; override with
`KLEINANZEIGEN_CHANNEL=chrome|msedge|bundled`.

## Configure the MCP client

The server speaks MCP over **stdio**.

```json
{
  "mcpServers": {
    "kleinanzeigen": {
      "command": "npx",
      "args": ["-y", "@washedguy/kleinanzeigen-mcp"]
    }
  }
}
```

From a checkout, point at the built entrypoint instead:

```json
{
  "mcpServers": {
    "kleinanzeigen": {
      "command": "node",
      "args": ["/path/to/kleinanzeigen-mcp/dist/index.js"]
    }
  }
}
```

## Tools

| Tool | Input | Returns |
| --- | --- | --- |
| `search_listings` | `query`, `location?`, `radiusKm?`, `minPrice?`, `maxPrice?`, `sort?`, `maxPages?` | `{ results }` |
| `get_listing` | `id?` or `url?` | `Listing` |

`sort` is `relevance` (default), `newest`, `price_asc` or `price_desc`.
`location` takes a city or postal code and combines with `radiusKm`. `maxPages`
(1–10) walks the numbered result pages and de-duplicates by listing id. Note that
promoted "Top-Anzeigen" are pinned to the top and ignore sorting.

### Errors

Failures come back as small, safe payloads — never browser stack traces:

```json
{ "error": "CHALLENGE_REQUIRED", "message": "Kleinanzeigen presented a challenge (e.g. CAPTCHA). Try again later from a normal browser session." }
```

Codes: `CHALLENGE_REQUIRED`, `NAVIGATION_ERROR`, `LISTING_NOT_FOUND`,
`RATE_LIMITED`, `INVALID_INPUT`, `INTERNAL_ERROR`.

## Examples

```ts
const { results } = await searchListings({ query: "Mac Mini M4", maxPrice: 500, sort: "price_asc" });
const listing = await getListing({ id: results[0].id });
```

## How it works

MCP and browser automation are strictly separated: the MCP layer only validates
inputs and formats outputs, and never touches the browser.

```text
src/
├── index.ts                 # stdio entrypoint
├── mcp/                     # tools, schemas, response shape
├── kleinanzeigen/           # browser session, page readers, parsing, domain models
└── types/  utils/
```

Search results and listings are parsed from Kleinanzeigen's server-rendered HTML
into domain models; raw HTML never reaches the MCP client.

## Debugging

```bash
kleinanzeigen-mcp-debug   # or: npm run debug
```

Opens a visible browser and saves every page (and client-side route change) as
HTML plus an `index.jsonl` into `./debug-snapshots/`. To capture from normal tool
calls instead:

```bash
KLEINANZEIGEN_SAVE_HTML=1 npm start
# or: KLEINANZEIGEN_SNAPSHOT_DIR=/tmp/ka-snaps npm run dev
```

Snapshots may contain public listing data and are git-ignored — do not commit
them.

## Development

```bash
npm run dev        # stdio server with watch mode
npm run verify     # typecheck + lint + tests (run before committing)
npm run check      # Biome lint/format/import-sort (write)
npm run test       # node:test via tsx
```

Linting and formatting use a single dev dependency
([Biome](https://biomejs.dev)); configuration is in `biome.json`.

## Security

- No login, credentials, cookies or tokens: the server only reads public pages.
- Sessions are not persisted.
- Challenges are never solved or bypassed: the server returns
  `CHALLENGE_REQUIRED`.
- No proxy rotation, no account creation, no CAPTCHA solving, no bulk scraping.
- Logs go to stderr only (stdout is the MCP transport).

## Limitations

- **Read-only, public listings.** No account features.
- **IP reputation is out of scope.** If Kleinanzeigen temporarily blocks your IP
  range, wait it out and reduce request frequency.
- Promoted listings appear above the requested sort order.

TDQS

A3.7/5.0

Scored across 2 tools

Disambiguation5/5

search_listings and get_listing have clearly distinct purposes: one performs a query and returns multiple summaries, the other retrieves a single listing by id or URL. There is no overlap or risk of misselection.

Naming Consistency5/5

Both tool names follow a consistent verb_noun snake_case pattern (search_listings, get_listing) and accurately reflect read-only behavior. The convention is predictable and unambiguous.

Tool Count3/5

With only two tools, the surface feels thin for a marketplace MCP. While search and detail retrieval are core, the server lacks ancillary tools that would make it feel well-scoped (e.g., category browsing or seller info).

Completeness4/5

The core read-only workflow is covered: users can search listings and retrieve full details for a single listing. Minor gaps exist, such as accessing seller profiles or navigating categories, but agents can work around them for basic browsing.

Maintenance

ActivityMaintained
ResponsivenessNo issues