---
name: micasai-mcp
description: >
  Verwende diesen Skill wenn du am micasAI MCP Server arbeitest oder wenn du
  Fragen über die Nutzung des MCP Servers in Claude Desktop beantwortest.
  Trigger: "mcp server", "claude desktop", "micasai_search", "semantic search mcp",
  "mcp tool", "mcp konfiguration", "user id token", "service account mcp".
---

# micasAI MCP Server – Skill

## Übersicht

Der MCP Server liegt in `micasai-mcp-server/` (separates Verzeichnis, kein Firebase-Repo).
Er verbindet Claude Desktop mit micasAI via:
1. **Firebase Admin SDK** → direkter Firestore-Zugriff (Properties, Documents, Tags)
2. **HTTP** → `semanticSearchDocuments` Cloud Function (Vertex AI Vector Search)

## Projektstruktur

```
micasai-mcp-server/
├── src/
│   ├── index.ts                  # Einstiegspunkt, MCP Server Init, stdio-Transport
│   ├── types.ts                  # Alle TypeScript-Interfaces (Spiegel Firestore-Schema)
│   ├── constants.ts              # IMPower Tag-Gruppen, Status-Labels, Limits
│   ├── services/
│   │   ├── firebase.ts           # Firebase Admin Init + alle Firestore-Queries
│   │   ├── semanticSearch.ts     # HTTP-Client für semanticSearchDocuments CF
│   │   └── formatter.ts          # Markdown-Formatierung für Tool-Antworten
│   └── tools/
│       ├── semanticSearch.ts     # micasai_search_documents
│       ├── documents.ts          # micasai_list_documents, _get_document_details, _get_document_ocr_text
│       ├── properties.ts         # micasai_list_properties, _get_property_stats
│       └── tags.ts               # micasai_list_document_types
├── dist/                         # Gebautes JavaScript (gitignore)
├── package.json
├── tsconfig.json
└── README.md
```

## Tool-Übersicht

| Tool | Datenquelle | Wichtigste Parameter |
|---|---|---|
| `micasai_search_documents` | `semanticSearchDocuments` CF | `query` (deutsch, frei), `limit` |
| `micasai_list_documents` | Firestore `documents/` | `property_id`, `document_type`, `status`, `limit`, `offset` |
| `micasai_get_document_details` | Firestore `documents/` | `document_id` |
| `micasai_get_document_ocr_text` | Firestore `documents/.ocrText` | `document_id` |
| `micasai_list_properties` | Firestore `properties/` | `search`, `administration_type` |
| `micasai_get_property_stats` | Firestore `documents/` (aggregiert) | `property_id` ODER `property_name` |
| `micasai_list_document_types` | Firestore `document_tags/` | `group_id` |

## Umgebungsvariablen (Claude Desktop `env`)

| Variable | Pflicht | Beschreibung |
|---|---|---|
| `FIREBASE_PROJECT_ID` | ✅ | z.B. `"micasai"` |
| `FIREBASE_SERVICE_ACCOUNT_JSON` | ✅ | Einzeiliger JSON-String. Wird sowohl für Firestore Admin als auch für OIDC-Auth der CF genutzt. |
| `SEMANTIC_SEARCH_URL` | ⚠️ | URL der `semanticSearchDocuments` Cloud Function (dient auch als OIDC-Audience) |
| `FIREBASE_USER_ID_TOKEN` | – | Optional, nur Dev-Fallback (1h Ablauf) |

## Wichtige Implementierungsdetails

### Auth für `semanticSearchDocuments`
Der Server authentifiziert standardmäßig mit einem **Google-OIDC-ID-Token aus dem Service
Account**. `google-auth-library` cached/refresht automatisch — kein manuelles Token-Handling.

- Implementierung: `src/services/semanticSearch.ts` → `GoogleAuth({ credentials }).getIdTokenClient(url)` → `getRequestHeaders()`
- Audience = `SEMANTIC_SEARCH_URL`
- CF-Seite verifiziert via `OAuth2Client.verifyIdToken({ idToken, audience })` und prüft `email.endsWith('.iam.gserviceaccount.com')`
- Optional auf CF-Seite: `MCP_EXPECTED_AUDIENCE`, `MCP_ALLOWED_SA_EMAIL`

Fallback: Wenn `FIREBASE_SERVICE_ACCOUNT_JSON` fehlt aber `FIREBASE_USER_ID_TOKEN` gesetzt ist, wird das User-Token als Bearer gesendet.

### Transport
Immer **stdio** (nicht HTTP) – Claude Desktop kommuniziert über stdin/stdout.
Logs IMMER auf `process.stderr`, nie `process.stdout`.

### Firestore-Queries in `firebase.ts`

**Properties-Lookup:**
```typescript
// Fuzzy-Match mit Umlaut-Normalisierung (ä→ae, ö→oe, ü→ue, ß→ss)
// Matching gegen: name, address.street, impowerHrId
// Ambiguität: Wenn 2+ Treffer mit Längen-Diff < 3 → null
```

**Documents-Query:**
```typescript
// Aktive Status: ['CLASSIFIED', 'NEEDS_REVIEW', 'CONFIRMED']
// documentType-Filter: Range-Query (>=, <=\uf8ff) für Präfix-Suche
// Sortierung: updatedAt desc
```

### Tool-Registrierung (Pattern)
```typescript
server.registerTool('micasai_tool_name', {
  title: '...',
  description: '...',     // Beschreibt Funktion, Args, Returns, Fehler
  inputSchema: z.object({ ... }).strict(),
  annotations: { readOnlyHint: true, destructiveHint: false, ... }
}, async (params) => {
  return {
    content: [{ type: 'text', text: markdownString }],
    structuredContent: data as Record<string, unknown>,  // Cast nötig!
  };
});
```

**Wichtig:** `structuredContent` erwartet `Record<string, unknown>` — bei eigenen Interfaces immer `as unknown as Record<string, unknown>` casten.

## Build & Deploy

```bash
cd micasai-mcp-server
npm install
npm run build          # → dist/index.js
```

**Claude Desktop Config** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
```json
{
  "mcpServers": {
    "micasai": {
      "command": "node",
      "args": ["/absoluter/pfad/dist/index.js"],
      "env": { ... }
    }
  }
}
```

Nach Config-Änderung Claude Desktop **neu starten**.

## Testen

```bash
# Dev-Modus: lädt .env via dotenv-cli und startet MCP Inspector + tsx
npm run dev

# MCP Inspector gegen gebauten Server
npx @modelcontextprotocol/inspector node dist/index.js

# TypeScript type check
npx tsc --noEmit
```

Für `npm run dev` muss eine `.env` im Projekt-Root liegen mit `FIREBASE_PROJECT_ID`,
`FIREBASE_SERVICE_ACCOUNT_JSON` und `SEMANTIC_SEARCH_URL` (gleiche Keys wie der
`env`-Block der Claude-Desktop-Config).

## Erweiterungen

### Neues Tool hinzufügen
1. Datei in `src/tools/` anlegen
2. `server.registerTool(...)` implementieren
3. In `src/index.ts` importieren und `registerXxxTools(server)` aufrufen
4. `npm run build`

### Neue Firestore-Collection abfragen
1. Interface in `src/types.ts` ergänzen
2. Query-Funktion in `src/services/firebase.ts` ergänzen
3. Tool in `src/tools/` erstellen

### semanticSearch ohne Token-Ablauf (bereits implementiert)
OIDC-ID-Token (JWT) statt OAuth-Access-Token — nur ersteres wird von der CF verifiziert:
```typescript
import { GoogleAuth } from 'google-auth-library';
const auth = new GoogleAuth({ credentials: JSON.parse(serviceAccountJson) });
const client = await auth.getIdTokenClient(semanticSearchUrl);  // audience = CF-URL
const headers = await client.getRequestHeaders();                // { Authorization: 'Bearer <jwt>' }
```
