Eigene Tools & Skills bauen – Teil 3 der Serie KI-Agenten in der Praxis
Wie du sichere Tools und Skills für KI-Agenten entwickelst – mit nachvollziehbaren Beispielen für OpenClaw, LangChain und MCP.
Ein KI-Agent, der nur antwortet, bleibt auf Textausgabe beschränkt. Werkzeuge erlauben ihm zusätzlich, Daten abzurufen, Dateien auszuwerten oder externe Dienste anzusteuern. Dabei bezeichnen Skill, Tool und Protokoll verschiedene Ebenen einer Lösung.
In OpenClaw beschreibt ein Skill, wann und wie vorhandene Tools, Skripte oder Hilfsdateien eingesetzt werden. In LangChain ist ein Tool typischerweise eine Python-Funktion mit einem maschinenlesbaren Eingabeschema. MCP standardisiert, wie Clients Werkzeuge eines getrennten Servers entdecken und aufrufen.
Die begrifflichen Grundlagen erklärt der Beitrag Was sind KI-Agenten?. Der Vergleich von LangChain, CrewAI und AutoGen ordnet die Framework-Ebene ein, auf der ein Teil der folgenden Tools eingesetzt wird.
Die drei Ebenen auf einen Blick
| Ebene | Hauptaufgabe | Führt selbst Code aus? |
|---|---|---|
| OpenClaw Skill | Arbeitsweise, Voraussetzungen und vorhandene Hilfsmittel beschreiben | Nicht automatisch |
| LangChain Tool | Eine Python-Funktion für einen Agenten aufrufbar machen | Ja, in der Python-Runtime |
| MCP Tool | Ein serverseitiges Werkzeug über ein Protokoll anbieten | Ja, im MCP-Server |
Ein Skill kann auf ein Skript oder Tool verweisen, erzeugt aber allein keinen API-Endpunkt. MCP eignet sich, wenn mehrere Clients oder Programmiersprachen dieselbe Tool-Implementierung verwenden sollen.
Welche Ebene passt zur Aufgabe?
Ein OpenClaw Skill passt zu lokalen Arbeitsanweisungen und vorhandenen Hilfsdateien. Ein LangChain Tool ist sinnvoll, wenn die Agentenlogik ohnehin in Python läuft. MCP trennt die ausführende Komponente vom Client und eignet sich dadurch für gemeinsam genutzte Integrationen.
Diese Ebenen lassen sich kombinieren. Ein Skill kann den sicheren Ablauf erklären, während ein Skript oder MCP-Tool die eigentliche Operation ausführt.
OpenClaw Skills: Anweisungen und Voraussetzungen
Ein OpenClaw Skill liegt in einem Verzeichnis mit einer SKILL.md. Die Datei besteht aus optionalem YAML-Frontmatter und einer Markdown-Anweisung. Laufzeitvoraussetzungen werden unter metadata.openclaw beschrieben.
Das folgende Beispiel setzt python3 und zwei Umgebungsvariablen voraus. Eine bezeichnet den Skill-Ordner, die andere den erlaubten Workspace:
---
name: csv-analyzer
description: Werte eine numerische Spalte aus einer CSV-Datei aus
metadata:
openclaw:
requires:
bins:
- python3
env:
- CSV_ANALYZER_SKILL_ROOT
- CSV_ANALYZER_WORKSPACE_ROOT
primaryEnv: CSV_ANALYZER_WORKSPACE_ROOT
---
Verwende diesen Skill für Summe, Durchschnitt oder Anzahl nicht leerer numerischer Werte in einer CSV-Spalte.
Führe das mitgelieferte Skript aus:
```bash
python3 "$CSV_ANALYZER_SKILL_ROOT/csv_stats.py" <datei> <spalte> <sum|average|count>
```
Sicherheitsregeln:
- Verändere die Eingabedatei nicht.
- Akzeptiere nur Dateien innerhalb von `CSV_ANALYZER_WORKSPACE_ROOT`.
- Gib Fehlermeldungen des Skripts unverändert und vollständig wieder.
Die Metadaten filtern den Skill anhand vorhandener Programme und Umgebungsvariablen. Sie ersetzen keine Berechtigungsprüfung im Skript: Ein Modellhinweis wie „verwende nur Workspace-Dateien“ verhindert technisch keinen Zugriff auf einen Außenpfad.
CSV_ANALYZER_SKILL_ROOT wird von der betreibenden Person auf das Verzeichnis des Skills gesetzt. Damit benötigt die Anleitung keinen besonderen Pfadplatzhalter. CSV_ANALYZER_WORKSPACE_ROOT bleibt davon getrennt und legt fest, welche Eingabedateien das Skript akzeptieren darf.
Sicheres CSV-Hilfsskript
Lege neben der SKILL.md eine Datei namens csv_stats.py an:
import csv
import math
import os
import sys
from pathlib import Path
ALLOWED_OPERATIONS = {'sum', 'average', 'count'}
if len(sys.argv) != 4:
raise SystemExit(
'Usage: python3 csv_stats.py <file> <column> <sum|average|count>'
)
root_value = os.environ.get('CSV_ANALYZER_WORKSPACE_ROOT')
if not root_value:
raise SystemExit('CSV_ANALYZER_WORKSPACE_ROOT is not set')
try:
workspace_root = Path(root_value).resolve(strict=True)
except (OSError, RuntimeError) as exc:
raise SystemExit(f'Invalid workspace root: {exc}')
if not workspace_root.is_dir():
raise SystemExit('Invalid workspace root: expected a directory')
try:
path = Path(sys.argv[1]).resolve(strict=True)
except (OSError, RuntimeError) as exc:
raise SystemExit(f'File not found or inaccessible: {exc}')
column = sys.argv[2]
operation = sys.argv[3]
if operation not in ALLOWED_OPERATIONS:
raise SystemExit(f'Unsupported operation: {operation}')
if not path.is_relative_to(workspace_root):
raise SystemExit('Access denied: file is outside the allowed workspace')
if not path.is_file():
raise SystemExit(f'Not a regular file: {path}')
values = []
with path.open(newline='', encoding='utf-8') as handle:
reader = csv.DictReader(handle)
if column not in (reader.fieldnames or []):
raise SystemExit(f'Column not found: {column}')
for line_number, row in enumerate(reader, start=2):
raw = (row.get(column) or '').strip()
if not raw:
continue
try:
value = float(raw)
except ValueError:
raise SystemExit(
f'Invalid number in column {column!r} on line {line_number}: {raw!r}'
)
if not math.isfinite(value):
raise SystemExit(
f'Non-finite number in column {column!r} on line {line_number}: {raw!r}'
)
values.append(value)
if operation == 'count':
print(len(values))
elif operation == 'sum':
print(sum(values))
else:
print(sum(values) / len(values) if values else 0)
Path.resolve(strict=True) kanonisiert den Root und die Eingabedatei. Der anschließende Vergleich weist absolute Außenpfade sowie Symlinks zurück, deren Ziel außerhalb des erlaubten Roots liegt. Die Workspace-Variable sollte von der betreibenden Person gesetzt werden und nicht aus einem unkontrollierten Tool-Argument stammen.
Das Skript verwendet Path.is_relative_to() und benötigt deshalb Python 3.9 oder neuer.
Testdatei anlegen und Ergebnis prüfen
Wechsle in den OpenClaw Workspace. Der folgende Block setzt beide Root-Variablen, legt eine eigene Testdatei an und führt die Summenberechnung aus. Eine bereits vorhandene Datei namens data/umsatz-tutorial.csv würde dabei überschrieben; prüfe den Zielpfad daher vor dem Kopieren des Befehls.
export CSV_ANALYZER_WORKSPACE_ROOT="$PWD"
export CSV_ANALYZER_SKILL_ROOT="$PWD/skills/csv-analyzer"
mkdir -p data
printf 'revenue\n10\n20.5\n4.5\n' > data/umsatz-tutorial.csv
python3 "$CSV_ANALYZER_SKILL_ROOT/csv_stats.py" data/umsatz-tutorial.csv revenue sum
Die Testdatei enthält den Header revenue und drei Datenzeilen. Die erwartete Ausgabe lautet:
35.0
Für einen kontrollierten Fehlertest kannst du eine zweite Datei mit einem ungültigen Wert anlegen:
printf 'revenue\n10\nNaN\n' > data/umsatz-tutorial-invalid.csv
python3 "$CSV_ANALYZER_SKILL_ROOT/csv_stats.py" data/umsatz-tutorial-invalid.csv revenue sum
Der Aufruf endet mit einer Meldung für Zeile 3:
Non-finite number in column 'revenue' on line 3: 'NaN'
Ein absoluter Pfad außerhalb von CSV_ANALYZER_WORKSPACE_ROOT sowie ein Symlink auf ein Ziel außerhalb dieses Roots enden mit:
Access denied: file is outside the allowed workspace
Wo OpenClaw Skills lädt
OpenClaw dokumentiert folgende Quellen in absteigender Priorität. Existiert derselbe Skill-Name mehrfach, gewinnt die höher priorisierte Quelle:
- Workspace Skills:
<workspace>/skills - Project Agent Skills:
<workspace>/.agents/skills - Personal Agent Skills:
~/.agents/skillsim Default-State - Managed oder lokale Skills:
<state-dir>/skills - Mit OpenClaw ausgelieferte Skills
- Zusätzliche Verzeichnisse aus
skills.load.extraDirssowie Plugin-Skills
Unter einem konfigurierten Root entdeckt OpenClaw eine SKILL.md auch in gruppierten Verzeichnissen bis zu sechs Ebenen tief. Für einen lokalen Workspace Skill ist <workspace>/skills/<skill-name>/SKILL.md der direkte Einstieg.
Prüfe die Erkennung mit:
openclaw skills list --verbose
Die Option --verbose zeigt zusätzliche Details einschließlich fehlender Voraussetzungen. Fehlt der Skill, kontrolliere Dateiname, Frontmatter, Skill-Root, python3, CSV_ANALYZER_SKILL_ROOT und CSV_ANALYZER_WORKSPACE_ROOT. Eine pauschale Gateway-Neustartreihenfolge ist für diese Diagnose nicht erforderlich.
Für einen sicheren Rückbau verschiebst du das Skill-Verzeichnis aus den geladenen Skill-Roots und prüfst anschließend erneut die Skill-Liste. Damit bleibt das Skript erhalten, wird aber nicht mehr als Skill geladen. Die beiden Tutorial-CSV-Dateien kannst du nach dem Test gezielt aus data/ entfernen.
LangChain Tools: Python-Funktionen mit Schema
LangChain erzeugt mit @tool aus einer Python-Funktion ein aufrufbares Werkzeug. Type-Hints, Docstring und optional ein Pydantic-Modell beschreiben die Eingaben.
Voraussetzungen installieren
Die unterstützte Python-Version und die Importpfade können sich zwischen LangChain-Versionen ändern. Prüfe deshalb vor der Installation die Anforderungen in der aktuellen LangChain-Dokumentation zu Tools und die Ausgabe deiner lokalen Python-Runtime:
python3 --version
python3 -m venv .venv
source .venv/bin/activate
python -m pip install langchain pydantic
python -m pip show langchain pydantic
python3 --version nennt die verwendete Python-Version. python -m pip show gibt unter anderem die tatsächlich installierten Paketversionen und Installationspfade aus. Bewahre diese Angaben zusammen mit deinem Projekt auf, wenn der Aufruf später unter denselben Abhängigkeiten wiederholbar sein soll.
Die folgenden Snippets sind Minimalbeispiele für die in der verlinkten Dokumentation beschriebenen Tool-Aufrufe. Sie sind kein versionsfixierter Reproduktionstest, weil der Installationsbefehl bewusst die zum Ausführungszeitpunkt verfügbaren Pakete bezieht.
Direkt aufrufbarer Wetter-Stub
Speichere dieses Beispiel als weather_tool.py:
from langchain.tools import tool
@tool
def weather_stub(city: str, units: str = 'metric') -> str:
"""Liefere feste Testdaten für die lokale Tool-Integration.
Args:
city: Stadtname.
units: metric oder imperial.
"""
if units not in {'metric', 'imperial'}:
raise ValueError('units must be metric or imperial')
value = '22 °C' if units == 'metric' else '72 °F'
return f'TESTDATEN – Wetter in {city}: {value}, sonnig'
print(weather_stub.invoke({'city': 'Berlin', 'units': 'metric'}))
Führe die Datei in der aktivierten virtuellen Umgebung aus:
python weather_tool.py
Wenn die installierte Version die dokumentierten Import- und Aufrufpfade verwendet, lautet die Ausgabe:
TESTDATEN – Wetter in Berlin: 22 °C, sonnig
Ein Importfehler bedeutet, dass die installierte LangChain-Version nicht zum gezeigten Dokumentationsstand passt. Vergleiche in diesem Fall die Ausgabe von python -m pip show langchain mit der aktuellen Tools-Dokumentation und passe den Importpfad an deren Beispiel an.
Der Name, der Docstring und die Ausgabe kennzeichnen die Funktion ausdrücklich als Stub. Für aktuelle Wetterdaten müsste die Funktion zusätzlich einen realen Anbieter aufrufen, dessen Antwort validieren und Netzwerkfehler behandeln.
Eingaben mit Pydantic begrenzen
Ein explizites Pydantic-Schema kann erlaubte Werte bereits vor dem Funktionsaufruf validieren:
from typing import Literal
from langchain.tools import tool
from pydantic import BaseModel, Field
class WeatherInput(BaseModel):
city: str = Field(min_length=1, description='Stadtname')
units: Literal['metric', 'imperial'] = Field(
default='metric',
description='Temperatureinheit',
)
include_forecast: bool = Field(
default=False,
description='Feste Testvorhersage ergänzen',
)
@tool(args_schema=WeatherInput)
def weather_stub_advanced(
city: str,
units: str = 'metric',
include_forecast: bool = False,
) -> str:
"""Liefere validierte Testdaten ohne externen Wetterabruf."""
value = '22 °C' if units == 'metric' else '72 °F'
forecast = '; TESTVORHERSAGE: morgen unverändert' if include_forecast else ''
return f'TESTDATEN – Wetter in {city}: {value}{forecast}'
print(
weather_stub_advanced.invoke(
{'city': 'Hamburg', 'units': 'metric', 'include_forecast': True}
)
)
Die erwartete Ausgabe bei kompatiblen Paketversionen lautet:
TESTDATEN – Wetter in Hamburg: 22 °C; TESTVORHERSAGE: morgen unverändert
Das Beispiel benötigt weder einen Store noch einen besonderen Runtime-Kontext. Kontext- und Langzeitspeicher sind eigene Integrationsschritte. Dafür müssen Context-Schema, Store-Implementierung und Agentenaufruf gemeinsam konfiguriert werden. Ein isoliertes ToolRuntime-Fragment wäre ohne dieses Setup nicht reproduzierbar.
MCP: Werkzeuge über ein Protokoll anbieten
Beim Model Context Protocol stellt ein Server Werkzeuge bereit, während eine LLM-Anwendung als Client darauf zugreift. Clients entdecken Werkzeuge über tools/list und rufen sie über tools/call auf. Die Eingaben eines Tools werden durch inputSchema beschrieben.
Eine Tool-Beschreibung kann beispielsweise so aussehen:
{
"name": "github_create_issue",
"description": "Erstelle ein GitHub-Issue",
"inputSchema": {
"type": "object",
"properties": {
"title": { "type": "string", "minLength": 1 },
"body": { "type": "string" },
"labels": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["title"],
"additionalProperties": false
}
}
Diese Definition ist ein Protokollbeispiel, kein vollständiger MCP-Server. Für einen ausführbaren Server kommen Transport, Initialisierung, Authentifizierung, Tool-Handler und die tatsächliche GitHub-Anbindung hinzu.
Zwei Fehlerklassen auseinanderhalten
Ein ungültiger JSON-RPC-Aufruf oder eine unbekannte Protokollmethode ist ein Protokollfehler. Ein Problem während einer korrekt gestarteten Tool-Ausführung gehört dagegen in das Tool-Ergebnis. Dazu zählen beispielsweise fehlende Berechtigungen oder eine fachlich abgelehnte Eingabe.
Ein fehlgeschlagener Tool-Aufruf kann ein strukturiertes Ergebnis mit isError: true liefern:
{
"content": [
{
"type": "text",
"text": "Issue konnte nicht erstellt werden: Repository ist schreibgeschützt."
}
],
"isError": true
}
Der Server muss Eingaben selbst validieren und Berechtigungen durchsetzen. Das vom Client gelieferte Schema ist keine Zugriffskontrolle. Schreibende Aufrufe sollten zusätzlich nachvollziehbar protokolliert und, falls nötig, an eine Freigabe gebunden werden.
Entscheidungsmatrix
| Kriterium | OpenClaw Skills | LangChain Tools | MCP Tools |
|---|---|---|---|
| Einbindung | OpenClaw-spezifisch | LangChain- beziehungsweise LangGraph-spezifisch | Protokollbasiert |
| Primärer Zweck | Arbeitsweise anleiten | Python-Funktion aufrufbar machen | Serverwerkzeug bereitstellen |
| Ausführung | Über vorhandene Tools, Skripte oder Plugins | In der Python-Runtime | Im MCP-Server |
| Schema | YAML-Frontmatter und Textanweisung | Type-Hints und optional Pydantic | JSON Schema |
| Verteilung | Skill-Verzeichnis in einem geladenen Root | Python-Paket oder Anwendungscode | Eigenständiger MCP-Server |
| Typischer Einsatz | Lokale Abläufe und Hilfsdateien | Python-native Agentenlogik | Gemeinsam genutzte Integrationen |
Sicherheitsregeln für eigene Werkzeuge
Ein neues Werkzeug sollte zunächst nur lesen. Schreibzugriffe folgen erst, wenn Eingaben, Rechte und Fehlerfälle kontrolliert sind.
Dateiwerkzeuge brauchen eine technisch erzwungene Root-Grenze. API-Schlüssel und Datenbankkonten erhalten nur die Rechte, die der konkrete Aufruf benötigt. Lösch-, Kauf-, Versand- oder Deployment-Aktionen sollten nicht allein durch eine Modellentscheidung ausgelöst werden.
Schreibende Tools profitieren außerdem von Idempotenzschlüsseln oder einer anderen Duplikaterkennung. Timeouts und Rate-Limits verhindern, dass wiederholte Agentenaufrufe einen Dienst überlasten. Audit-Logs sollten Ziel, Zeitpunkt, Ergebnis und freigebende Identität erfassen, aber keine Secrets speichern.
Wenn ein Beispiel nicht funktioniert
Bei einem nicht erkannten OpenClaw Skill liefert openclaw skills list --verbose Hinweise auf fehlende Programme oder Umgebungsvariablen. Prüfe außerdem, ob die Datei tatsächlich SKILL.md heißt und unter einem geladenen Root liegt.
Scheitert das CSV-Skript, lies die ausgegebene Zeilennummer. Access denied bedeutet, dass der kanonisierte Dateipfad außerhalb des konfigurierten Workspace liegt. Das gilt auch für einen Symlink, der nach außen zeigt.
Bei LangChain-Importfehlern muss die virtuelle Umgebung aktiv sein. python -m pip show langchain pydantic zeigt, ob beide Pakete in dieser Umgebung installiert sind und welche Versionen tatsächlich verwendet werden. Die Wetterfunktionen verwenden absichtlich keine Netzwerkverbindung; eine feste Ausgabe ist daher das erwartete Ergebnis.
Bei MCP muss geklärt werden, ob der Fehler auf Protokollebene oder während der Tool-Ausführung entstand. Nur die zweite Klasse wird als Tool-Ergebnis mit isError: true zurückgegeben.
Kernpunkte
Ein OpenClaw Skill beschreibt den Ablauf und die benötigten Fähigkeiten. Seine Textanweisung ersetzt keine technische Zugriffskontrolle. Das CSV-Beispiel setzt die Workspace-Grenze deshalb im Skript durch und weist ungültige Zahlen mit Zeilennummer zurück.
LangChain Tools binden Python-Funktionen direkt in eine Agentenruntime ein. Ein versionsfixierter Test muss Python- und Paketversionen festhalten; die gezeigten Snippets orientieren sich stattdessen an der jeweils aktuellen Tools-Dokumentation. MCP trennt Client und ausführenden Server. Schema-Validierung und Berechtigungen bleiben Aufgaben des Servers.
Die passende Ebene ergibt sich aus dem Einsatzort: lokale Arbeitsweise im Skill, Python-Code im LangChain Tool und gemeinsam genutzte Funktionen hinter einem MCP-Server. Im vierten Teil geht es weiter mit Multi-Agent-Systemen und ihrer Orchestrierung.
Transparenz
agentenlog.de nutzt KI-Assistenz für Recherche, Struktur und Entwurf. Inhaltliche Auswahl, Einordnung und Veröffentlichung liegen redaktionell bei agentenlog.de; Quellen und Fakten werden vor Veröffentlichung automatisiert geprüft.
Quellen
Serie: KI-Agenten in der Praxis
Das könnte dich auch interessieren
OpenClaw Tutorial Teil 5: Skills & Tools erweitern
Praktischer Guide: OpenClaw Skills finden, einordnen und eigene Skills schreiben – von SKILL.md bis zu sicheren Tool-Abläufen.
Multi-Agent-Systeme: Wenn KIs zusammenarbeiten — Teil 4 der Serie „KI-Agenten in der Praxis“
Wie du Multi-Agent-Workflows mit klaren Rollen, strukturierten Übergaben und OpenClaw als Laufzeit-, Gateway- und Konfigurationsschicht planst.
Was sind KI‑Agenten? (Und was nicht)
Grundlagen zu KI-Agenten, ReAct und Tool-Use: was echte Agenten von Chatbots unterscheidet und worauf es in der Praxis wirklich ankommt.