In-process adapter library for Python MCP servers that enables structured feedback collection on tool usefulness, clarity, and fit
Project description
Skald - MCP Feedback Adapter
"A storyteller for your tools' performance, letting them speak back through AI agents when they shine or stumble."
Skald is an in-process adapter library for Python MCP servers that enables structured feedback collection on tool usefulness, clarity, and fit from AI agents.
Features
- Transparent Tool Wrapping: Drop-in wrapper that intercepts MCP tool calls and adds trace IDs with metrics collection
- Structured Feedback Collection: New
feedback.reporttool with strict JSON schema validation - Smart Feedback Invitations: Automatically invites feedback based on errors, latency, and output size
- Opt-out Controls: Decorator and context manager for suppressing feedback collection
- Multiple Storage Backends: SQLite default with optional Parquet export and data redaction
- Multiple Transports: Support for stdio, TCP, and Unix socket transports
Installation
pip install skald
Quick Start
Basic Usage
import skald
from your_mcp_server import YourMCPServer
# Wrap your existing MCP server
upstream = YourMCPServer()
survey = skald.SurveyingProxy(
upstream=upstream,
store="sqlite:///feedback.db",
invite_policy={"error": True, "p95_ms": 5000, "large_output_kb": 256},
sample_neutral=0.10,
ttl_hours=24
)
# Your server now collects feedback transparently
Using Opt-out Controls
# Opt out specific functions
@survey.opt_out(reason="contains sensitive data")
def run_sensitive_query(sql: str) -> dict:
return execute_query(sql)
# Suppress collection for a block of code
with survey.suppressed(reason="benchmark"):
for _ in range(1000):
call_some_tool()
Running a Server
from skald.transport import TCPTransport
# Create transport and serve
transport = TCPTransport(survey)
await transport.serve(host="0.0.0.0", port=8765)
CLI Usage
Skald includes a CLI for running demo servers and querying feedback data:
# Run over TCP
skald tcp --host localhost --port 8765
# Run over stdio
skald stdio
# Query stored feedback
skald query --store sqlite:///feedback.db --limit 10
# Clean up expired data
skald cleanup --store sqlite:///feedback.db --ttl-hours 24
Agent Integration
To collect feedback from AI agents, add this line to your system prompt:
When a tool provides a trace_id in its response metadata and invite_feedback=true, you may optionally use feedback.report to provide structured feedback on the tool's helpfulness (1-5), fit (1-5), clarity (1-5), confidence (0.0-1.0), and up to 3 short suggestions for improvement.
Feedback Schema
The feedback.report tool accepts this JSON schema:
{
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"helpfulness": 4,
"fit": 3,
"clarity": 5,
"confidence": 0.8,
"better_alternative": "different_tool",
"suggestions": ["Try tool X", "Use parameter Y"],
"notes": "This worked well overall"
}
Fields
trace_id(required): UUID from the tool response metadatahelpfulness(required): How helpful was the tool (1-5)fit(required): How well did the tool fit the task (1-5)clarity(required): How clear was the tool output (1-5)confidence(required): Confidence in this feedback (0.0-1.0)better_alternative(optional): Enum suggesting better alternativessuggestions(optional): Up to 3 suggestions, each ≤100 charactersnotes(optional): Additional notes
Configuration
Invite Policy
Control when feedback is invited:
invite_policy = {
"error": True, # Invite on errors
"timeout": True, # Invite on timeouts
"p95_ms": 5000.0, # Latency threshold (ms)
"large_output_kb": 256.0 # Output size threshold (KB)
}
Storage Configuration
# SQLite (default)
store = "sqlite:///path/to/feedback.db"
# Custom storage backend
from skald.storage.sqlite import SQLiteStorage
storage = SQLiteStorage("/custom/path.db")
survey = SurveyingProxy(upstream, store=storage)
Data Redaction
Configure custom data redaction:
def custom_redactor(args: dict) -> dict:
"""Remove sensitive data from tool arguments."""
redacted = args.copy()
if 'password' in redacted:
redacted['password'] = '[REDACTED]'
return redacted
survey = SurveyingProxy(
upstream=upstream,
redactor=custom_redactor
)
Storage Schema
Skald uses two main tables:
tool_runs
trace_id(PK): UUID trace identifiertimestamp: Execution timestampagent_id: ID of the calling agenttool_name: Name of the tool calledstatus: success/error/timeoutlatency_ms: Execution latency in millisecondsoutput_bytes: Size of output in bytesinvite_feedback: Whether feedback was invitedopt_out: Whether collection was opted outargs_redacted: Redacted tool arguments (JSON)
tool_feedback
trace_id(FK): Reference to tool_runsagent_id: ID of the agent providing feedbackhelpfulness: Helpfulness rating (1-5)fit: Fit rating (1-5)clarity: Clarity rating (1-5)confidence: Confidence score (0.0-1.0)better_alternative: Better alternative suggestionsuggestions: List of suggestions (JSON)notes: Additional notesvalid: Whether feedback passed validationraw_json: Raw feedback JSONtimestamp: Feedback timestamp
Architecture
Skald follows a clean architecture with these components:
- Core:
SurveyingProxy- Main proxy class that wraps MCP servers - Schema: Pydantic models for data validation
- Storage: Pluggable storage backends (SQLite default)
- Transport: Communication protocols (stdio, TCP, Unix sockets)
- Decorators: Opt-out mechanisms (
@opt_out,with suppressed())
Development
Setup
git clone https://github.com/sibyllinesoft/skald.git
cd skald
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
uv pip install -e ".[dev,test]"
Testing
pytest
pytest --cov=skald --cov-report=html
Code Quality
black skald tests
isort skald tests
mypy skald
ruff skald tests
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sibylline_skald-0.1.0.tar.gz.
File metadata
- Download URL: sibylline_skald-0.1.0.tar.gz
- Upload date:
- Size: 43.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5723dee2274fcc20192afb3d35db8532d556ebbcf95b80b04164f0ddc9d9e6b2
|
|
| MD5 |
1531b1714a9cbdada7473c98754ab690
|
|
| BLAKE2b-256 |
ca2e2f1f6402e0faa7c867c73c8218422d2e3a3748ce2cffa190deed9b593734
|
File details
Details for the file sibylline_skald-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sibylline_skald-0.1.0-py3-none-any.whl
- Upload date:
- Size: 48.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca54fb4995ef28c96ca4b6e846dbb8a8f6a5e255897157917a05df641d7f9907
|
|
| MD5 |
51399eccaa8dc8dca1335f58dd71fe18
|
|
| BLAKE2b-256 |
09cbb8a326c011bed3704e60f447171d2ab41ce00dcbb7b73c16c2a287bb653b
|