Skip to main content

Universal Execution Monitoring & Feedback Library for Python applications, supporting MCP servers, functions, shell commands, and more

Project description

Skald - MCP Feedback Adapter

PyPI version Python versions Tests Coverage Code style: black Ruff

"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.report tool 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 metadata
  • helpfulness (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 alternatives
  • suggestions (optional): Up to 3 suggestions, each ≤100 characters
  • notes (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 identifier
  • timestamp: Execution timestamp
  • agent_id: ID of the calling agent
  • tool_name: Name of the tool called
  • status: success/error/timeout
  • latency_ms: Execution latency in milliseconds
  • output_bytes: Size of output in bytes
  • invite_feedback: Whether feedback was invited
  • opt_out: Whether collection was opted out
  • args_redacted: Redacted tool arguments (JSON)

tool_feedback

  • trace_id (FK): Reference to tool_runs
  • agent_id: ID of the agent providing feedback
  • helpfulness: 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 suggestion
  • suggestions: List of suggestions (JSON)
  • notes: Additional notes
  • valid: Whether feedback passed validation
  • raw_json: Raw feedback JSON
  • timestamp: 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sibylline_skald-0.2.0.tar.gz (50.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sibylline_skald-0.2.0-py3-none-any.whl (55.8 kB view details)

Uploaded Python 3

File details

Details for the file sibylline_skald-0.2.0.tar.gz.

File metadata

  • Download URL: sibylline_skald-0.2.0.tar.gz
  • Upload date:
  • Size: 50.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for sibylline_skald-0.2.0.tar.gz
Algorithm Hash digest
SHA256 e48c38af899cd11f8abe34bed1d6f6cf17179dfc26417f1f50696715ebc95e41
MD5 da4867b93a42709eb090250babde0f45
BLAKE2b-256 00c71a33195a4f97afe5b5e2e6f42b3aadffac73b45b1600f7e3cce48aeb65c0

See more details on using hashes here.

File details

Details for the file sibylline_skald-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for sibylline_skald-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 eca2fb12d7224fe4c95ca03b16db6657cb9511fe6f2572b6dc3f7783e286734d
MD5 f8902d52af4dd7646481a2d11748de80
BLAKE2b-256 aa18be53dcf807ccd51244b39a9b2e0ecbcfa05501e302d274c84de4ef02ea1e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page