Skip to main content
Glama
roshano3o3

mcp-toolserver

by roshano3o3

mcp-toolserver

Ein MCP-Server, der vier echte Tools bereitstellt (Dokumentsuche, SQL, Arithmetik, Korpus-Introspection), sowie einen Agent-Client, der sich mit ihm verbindet, diese Tools zur Laufzeit entdeckt und sie mit Claude verkettet, um Fragen zu beantworten, die kein einzelnes Tool allein beantworten könnte.

Was hier demonstriert wird

  • Das Model Context Protocol — ein offenes Protokoll (Anthropic, Nov. 2024), das standardisiert, wie eine KI-Anwendung sich mit externen Tools und Daten verbindet. Ohne es brauchte jede KI-App für jedes Tool eine eigene Integration und jedes Tool bräuchte für jede KI-App eine eigene Integration — ein N×M-Problem. MCP macht daraus N+M: Ein Tool-Anbieter baut einen einzigen MCP-Server, und jeder MCP-kompatible Client kann ihn ohne maßgeschneiderten Integrationscode nutzen. Dieses Repo ist ein kleines, konkretes Beispiel dafür: Server und Client kennen hier keine Interna des jeweils anderen, sondern nur das Protokoll zwischen ihnen.

  • Dynamische Tool-Erkennung — der Agent-Client hartkodiert nie eine Tool-Liste. Er ruft beim Verbindungsaufbau list_tools() auf und überführt das, was der Server gerade anbietet, in das Tool-Use-Format von Anthropic. Fügen oder mobiles Tool auf dem Server hinzu und der Client übernimmt es automatisch — ohne clientseitige Codeänderung.

  • Mehrstufige Tool-Verkettung — eine einzige Frage kann zwei verschiedene Tools in Folge erfordern (erst einen Wert nachschlagen, dann damit rechnen), und die Agent-Schleife regelt das selbst: Claude entscheidet, ein zweites Tool aufzurufen und nutzt dabei das Ergebnis des ersten, ohne dazu aufgefordert worden zu sein.

Related MCP server: Sentinel Core Agent

Die vier Tools

Tool

Signatur

Was es tut

search_documents

(query: str, top_k: int = 5) -> list[dict]

Semantische Suche über docminds eingelesenen PDF-Korpus (dichte Embeddings + Chroma). Liefert pro Chunk {text, source, page, score}.

query_database

(sql: str) -> list[dict]

Read-only-SQL gegen eine kleine, vorbefüllte Demo-Datenbank (employees, departments). Nur SELECT — siehe Sicherheit weiter unten.

calculate

(expression: str) -> float

Arithmetische Auswertung (+ - * / ** %, Klammern) ohne jeglichen Code-Ausführungspfad — siehe Sicherheit weiter unten.

list_documents

() -> list[dict]

Inventar des eingelesenen Korpus: {source, pages, chunks} pro Dokument.

Der Docstring jedes Tools ist seine MCP-Beschreibung — das ist es, was das LLM tatsächlich liest, um zu entscheiden, wann es das Tool aufrufen soll: Die Texte sind also für dieses Publikum geschrieben, nicht für Menschen, die den Quell überfliegen.

Live-Demo-Runs

Alle drei unten sind echte Runs echte Läufe gegen die echte Claude-API (source ist) und einen echten gestarteten MCP-Server-Subprozess — nicht erfundene Traces. Zuerst der Fall, der tatsächlich zwei Tools verkettet, denn das ist der interessante Fall.

1. Mehrstufig: query_database → calculate

„Wie hoch ist das Durchschnittsgehalt in Engineering, und was würde eine 12-Prozent-Erhöhung insgesamt kosten?“

Answer:
Here's the breakdown for the Engineering department:

| Metric | Value |
|---|---|
| Average Salary | $141,600 |
| Total Current Payroll | $708,000 |
| Cost of 12% Raise | $84,960 |
| New Total Payroll | $792,960 |

A 12% raise across all Engineering employees would cost an additional $84,960,
bringing the department's total payroll from $708,000 to $792,960.

Iterations: 3
Tool calls:
  1. query_database({'sql': "SELECT AVG(salary) as avg_salary, SUM(salary) as total_salary FROM employees WHERE department_id = (SELECT id FROM departments WHERE name = 'Engineering')"})
     -> [{'avg_salary': 141600.0, 'total_salary': 708000}]
  2. calculate({'expression': '708000 * 0.12'})
     -> 84960.0

Claude hat das SQL selbst geschrieben, das Ergebnis gelesen und dann den arithmetischen Ausdruck selbst geschrieben und ausgeführt — die Tool-Eingaben oben stammen von Claude und sind nicht hartkodiert. (Von Hand überprüft: 5 Engineering-Mitarbeitende in den Seed-Daten mit insgesamt $708.000, ÷5 = $141.600 Durchschnitt, ×0,12 = $84.960.)

2. Einzel-Tool: query_database

„Wie viele Mitarbeitende gibt es in der Abteilung Engineering?“

Answer:
There are 5 employees in the Engineering department.

Iterations: 2
Tool calls:
  1. query_database({'sql': "SELECT COUNT(*) as employee_count FROM employees e JOIN departments d ON e.department_id = d.id WHERE d.name = 'Engineering'"})
     -> [{'employee_count': 5}]

„Was ist corrective RAG?“

Answer:
## Corrective RAG (CRAG)

Corrective RAG (CRAG) is an enhanced version of standard Retrieval-Augmented
Generation (RAG) that adds a self-correction step after the initial retrieval
phase. [...] Standard (vanilla) RAG simply takes the top-k retrieved documents
and passes them directly to the language model generator -- regardless of
whether those documents actually answer the question. CRAG improves on this by
checking retrieval quality before generation.

[... full answer continues with the retrieve -> grade -> (generate | rewrite &
retry) flow and the latency/LLM-call tradeoff, condensed here for length ...]

Iterations: 2
Tool calls:
  1. search_documents({'query': 'corrective RAG'})
     -> [5 chunks from langgraph_agents.pdf and llm_evaluation.pdf, scores 0.44-0.58]

Die Antwort stützt sich auf den tatsächlich abgerufenen Dokumenttext (docminds langgraph_agents.pdf) – nicht auf Allgemeinwissen über CRAG, das Claude ebenfalls hat, aber hier nicht nutzen sollte.

Sicherheit

query_database — mehrschichtig, nicht auf einen einzelnen Pin verlassen:

  1. App-seitige Schlüsselwort-/Strukturprüfung — alles, was keine einzelne SELECT-Anweisung (oder WITH ... SELECT) ist, lehnt flint bereits ab, bevor es überhaupt zu SQLite gelangt. Blockiert SELECT masked, UPDATE, DELETE, DROP, ALTER, CREATE, ATTACH, DETACH, PRAGMA, VACUUM, REINDEX und lehnt mehrere Anweisungen (;-getrennt) komplett ab.

  2. Natives Read-only-Mode von SQLite — die Verbindung selbst wird per ?mode=ro in der URI geöffnet. Das erzwingt die SQLite-Engine, nicht der Anwendungscode und ist damit die eigentliche Rückfallebene, falls Schritt 1 eine Lücke hat: Selbst eine Abfrage, die die Schlüsselwort-Prüfung überwindet, kann physisch nichts schreiben.

  3. Zeilenlimit — jede Abfrage als SELECT * FROM (<query>) LIMIT 500 gekapselt, also kann keine Abfrage mehr als 500 Zeilen zurückgeben, egal was sie verlangte.

  4. Echtzeit-Timeout — Ein sqlite3-ProgressHandler misst die verstrichene Zeit und bricht die Anweisung ab, wenn sie minnestens zu lang läuft.

calculate — AST-Allowlist, kein eval(): Der Ausdruck wird mit ast.parse(..., mode="eval") geparst und von Hand per AST durchlaufen; nur Constant (numerisch), BinOp (+ - * / ** %) und UnaryOp (+/-) sind zulässig. Alles andere — etwa ein Name-Lookup, ein Call oder ein Attribute — hat keinen korrespondierenden Zweig im Walker und wirft konstruktionsbedingt einen ValueError. Deshalb scheitert calculate("__import__('os').system('...')"): Das wird nicht gegen eine Blockliste gefährlicher Aufrufe gematcht, sondern es existiert schlicht nie ein Codestrombast, der einen Call ausführt.

Proof-Entscheidungen

  • Explizite Agent-Schleife ist besser, statt des Beta-Tool-Runner des Anthropic SDKs. Das SDK liefert damit eine MCP-Brücke (anthropic.lib.tools.mcp) die MCP-Tools direkt in den Tool-Runner steckt. Sie wird hier nicht genutzt, das das Ziel ein konkreter, vollständiger Extra-Wert warkt genau der riechende {answer, tool_calls: [{tool, input, output}], iterations} ist, der eine umlaufende Protokollierung um jede Runde selbst gebaut braucht. Man könnte vom Tool-Runner genau solche Mechanarbeitende (Schleifensteuerung, Trace pro Call) versteckt bekommen, die dieses Projekt gezeigt haben will.

  • stdio-Transport, nicht streamable-http. Der Client startet den Server bei Bedarf als Subprozess; beide liegen in der selben Vertrauenszone und es gibt keinen Netzwerk-Betrug. Damit passt die Einfachheit von stdio (keine Ports, keine Auth-Erfordernis). Streamable-http wird unterstützt (--transport streamable-http beziehungsweise Env-Variable MCP_TRANSPORT) für den Fall wirklich getrennter Prozesse/Maschinen — aber dafür ist hier nichts abgesichert worden (siehe Einschränkungen).

  • Many 8-Guard-Kapazität. Das Sanktl darstellt, im Worst-Case-Kosten und Latenz einer runaway-Schleife —derselbe Gedanke wie docminds Rewrite-Cap. Alle drei Demoläufe oben waren nach 2-3 Iterationen fertig; 8 ist eine großzügige Obergrenze, die echte kaputte prompts oder instabilmodelles Verhalten abfangen soll, keine Grenze, die im normalen Betrieb erreicht werden dürfte.

Verbindung zu docmind

search_documents und list_documents lesen die persistent Chroma-Collection von docmind direkt, (DOCMIND_CHROMA_PATH, per Default auf data/chroma under dem Schwesterprojekt docmind gerichtet). Für die Einbettungen wird dasselbe Modell all-MiniLM-L6-v2 gewählt, das docmind zum Ingestion-Zeitpunkt nutzte. Verbinandung ist frei von docmind-Spezifika, auf Code-Ebene: nur eine Chroma-Sammlung an einem konfigurierten Pfad. Dieses Projekt ist deshalb ein echter zweiter Client diesem Korpus, nicht eine Kopie. Es belegt: Die Retrieval-Ebene von docmind hängt nicht spezifisch an doc dem FastAPI-Backend von docmind; bei Address ist der Zuend ever durch jeden MCP-fähigen Client, der weiß, where die Sammlung liegt.

Bekannte Einschränkungen

  • Die Demo-SQL-Datenbank ist klein und synthetisch (12 Mitarbeitende, 4 Abteilungen) — hier wurde nichts gegen eine reale Skala oder feindselige DB getestet.

  • Die SQL-Blockliste ist ein Regex auf den Querytext, kein echter SQL-Parser — sie kann überblockieren (etwa einen legitimen Verweis auf die Table-Wert-Funktion pragma_table_info()) und im Prinzip auch ein Konstruct verpassen, dasz niemand aufschlauen. Der Readonly-Verbindungsmodus ist die Verteidigungslinie, die nicht davon abhängt, dass die Blockliste vollständig ist.

  • calculate unterstützt nur literale Zahlen und die sechs aufgeführten Operatoren — keine Funktionen (sqrt, sin, …), keine Variablen. Bewusst minimal, kein allgemeiner Ausdrucks-Engine.

  • Der MCP-Server hat keine Authentifizierung. Für stdio okay (Prozess-lokal, eine einzelne Trust-Boundary); im laufenden Zustand über streamable-http kann jede Person, die den Port erreicht, jedes Tool aufrufen, auch query_database.

  • Das Uncut 8-Iterations-Limit ist ein harter Stopp, kein sanfter Verlust: Eine Frage, deren Lösung wirklich mehr als etwa 4 Tool-Bounces braucht, bekommt eine Meldung „stopped after 8 iterations“ statt einer echten Antwort.

  • Keine einheitenspeicher über CLI-Aufrufe hinweg — Clean jeder python -m toolserver.client.agent "..."-Aufruf eine frische Konversation ohne Verlauf.

  • Kein Streaming — jede Loop-Iteration ist ein Blocking messages.create-Aufruf; ein langsamer Tool/eine langsame Generierung blockiert die komplette Runde.

  • Die Tests mocken den Anthropic-Client vollständig und das MCP Schluss ebenso (by design — keine echten API-Attribute im Testsuite). Das bedeutet: Schema-Abweichungen in einem der SDKs werden von pytest allein nicht gefunden; die Live-Demo-Läufe oben sind die einzige Echt-API-Prüfung und sind manuell, nicht CI-Teil.

Setup und Run

Erfordert Python 3.12, ANTHROPIC_API_KEY und (für search_documents/list_documents) ein docmind-Checkout, dessen Korpus bereits ingest benutzt wurde.

git clone https://github.com/roshano3o3/mcp-toolserver.git
cd mcp-toolserver
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -e .
cp .env.example .env              # edit .env and set ANTHROPIC_API_KEY

Standardmäßig zeigt DOCMIND_CHROMA_PATH in .env.example auf data/chroma eines Schwester-docmind-Checkouts. Anpassen an den Punkt, wo docminds Korpus liegt, oder einfach ignorieren — query_database, calculate und die Fehlerpfade für list_documents funktionieren ohne überhaupt docmind im Spiel.

Start den Agenten – der startet den MCP-Server selbst gleich als Subprozess, es muss kein eigener Server-Prozess laufen:

python -m toolserver.client.agent "How many employees are in the Engineering department?"

Oder den MCP-Server allein starten, zum Beispiel um einen anderen MCP-Client darauf zu pointen:

`

python -m toolserver.server                      # stdio (default)
python -m toolserver.server --transport streamable-http   # http://127.0.0.1:8765/mcp by default

Tests:

pytest
ruff check .

Verified gegen das installierte SDK (nicht neu aus dem Gedächtnis geschrieben)

mcp==2.0.0 unterscheidet ich deutlich von alten mcp.server.fastmcp.FastMCP-Apis — “`” dies momentan existiert nicht. Alles unten wurde durch Lesen des Quelltexts des installierten Pakets und Live-Smoke-Tests bestätigt (in-process und echter stdio), nicht von Speicherwissen:

  • Server: from import … from mcp.server.mcpserver import MCPServerMCPServer("name"), Tools werden mit @server.tool() registriert (bei Station Pflicht; @server.tool ohne Klammernt wirft absichtlich Fehler). server.run(transport="stdio" | "sse" | "streamable-http").

    • Client-l: from mcp.client import Client – Der neue unified client, der für die meisten Fälle eine direkte ClientSession ersetzt. Nimmt in-process Server/MCPServer, eine URL als String oder ein Transport (z. B. -stdio_client(StdioServerParameters(...)).

  • Discovery: await client.list_tools()ListToolsResult, jedes Tool trägt name, description, input_schema — same Feldnamen, die Anthropic’s Nutzungsformat für Tools von Eingaben erwartet, so die Umrechnung praktisch eine direkte 1:1-Mapping statt einem Schema-Übersetzer.

  • Tool-Ergebnis: CallToolResult bringt sowohl .рів content (Liste von MCP-Contentblocks, immer befüllt) als auch .structured_content (typisiertes {"result": ...}-Dict, gefüllt wenn die Tool-Function eine Return-Typannotation trägt — für alle vier hier beschriebenen anderen).

F
license - not found
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables Claude Code to perform programmatic tool calling by executing Python scripts that interact with multiple MCP servers in a single round-trip. This reduces latency and token consumption by keeping intermediate tool results within the local Python runtime instead of the conversation context.
    1
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables automatic discovery and reuse of tools from Claude Code execution traces. Provides MCP tools that are distilled from real work, allowing you to reuse previously written scripts without manual effort.
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables document ingestion and typed knowledge graph queries through Claude MCP tools, allowing agents to extract, store, and retrieve typed entities and relations from documents.
    2
    MIT

View all related MCP servers

Related MCP Connectors

  • Free OpenAI-compatible inference with signed provenance receipts and 3 focused MCP tools.

  • Hosted MCP with 91 agent tools: X, domains, SEO, Maps, Trends, Search, YouTube, TikTok, and more.

  • AI Reasoning Cache & Consensus Layer with 11 MCP tools via Streamable HTTP.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/roshano3o3/mcp-toolserver'

If you have feedback or need assistance with the MCP directory API, please join our Discord server