trail-spec
Health Pass
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 10 GitHub stars
Code Pass
- Code scan — Scanned 6 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
TRAIL — Tracking Records Across Isolated Logs. An open protocol for cross-MCP content tracking and deduplication.
An open protocol for cross-MCP server content tracking and deduplication.
Spec (EN) · Spec (RU) · Examples · README на русском
The Problem
You have multiple MCP servers — one fetches content, another posts to Telegram, a third cross-posts to a social network. Each server is isolated by design. No server sees the others.
Now answer these questions:
- Was this image already posted to Telegram?
- Did the social network post succeed or fail?
- Where did the pipeline break yesterday at 3am?
You can't. There's no standard way to track content across isolated MCP servers.
TRAIL solves this.
How It Works
LLM Orchestrator (Claude, GPT, etc.)
/ | \
/ | \
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Civitai │ │ Telegram │ │ Facebook │
│ MCP │ │ MCP │ │ MCP │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
trail.jsonl trail.jsonl trail.jsonl
Each server keeps its own trail.jsonl — an append-only log with a shared schema. The orchestrator reads all logs and connects the dots using a universal Content ID (content_id).
One pipeline run across three servers:
# Civitai trail.jsonl
{"version":2,"timestamp":"2026-04-05T14:07:00Z","content_id":"civitai:image:12345","action":"selected","requester":"daily-post","trace_id":"run-001"}
# Telegram trail.jsonl
{"version":2,"timestamp":"2026-04-05T14:07:05Z","content_id":"civitai:image:12345","action":"posted","requester":"daily-post","trace_id":"run-001","details":{"platform":"telegram","platform_id":"42"}}
# Facebook trail.jsonl
{"version":2,"timestamp":"2026-04-05T14:07:30Z","content_id":"civitai:image:12345","action":"posted","requester":"daily-post","trace_id":"run-001","details":{"platform":"facebook","platform_id":"99"}}
The orchestrator sees: civitai:image:12345 → selected → posted to Telegram (#42) → posted to Facebook (#99). Full pipeline traced via trace_id.
Key Features
- Zero shared state — no database, no message bus, no inter-server communication
- Append-only JSONL — atomic writes, no corruption risk, trivially parseable
- Self-documenting — readable field names:
content_id,action,requester,timestamp,version - Universal Content ID —
source:type:idformat traces content across any number of servers - Trace correlation — optional
trace_idlinks entries across servers into one pipeline trace - 15 standard actions —
fetched,selected,posted,failed,skipped,retrying,transformed,moderated,expired,delivered,delegated,received,evaluated,guarded,acknowledged - Multi-agent patterns — delegation, evaluation, guardrails, and human-in-the-loop via standard actions and
caused_bychains - Standard tools —
get_trail,mark_trail,get_trail_stats— same API everywhere - Standardized details — error types, cost tracking, content metadata, platform IDs, guardrail results, evaluation scores
- Auto-logging — publishing tools log automatically when
content_idandrequesterare passed - 3 conformance levels — Basic (5 fields + 2 tools), Standard (+ tracing, auto-logging, discovery), Full (+ causality chains, OTel export, all 15 actions)
- Zero dependencies — stdlib only, no external packages
- OTel-native mapping —
caused_by→parentSpanId,server→service.name, full span tree in any OTel backend
Quick Start
Python
from trail import Trail
trail = Trail("./data")
# Log an event
await trail.append(
content_id="civitai:image:12345",
action="posted",
requester="daily-post",
details={"platform": "telegram", "platform_id": "42"},
trace_id="run-001"
)
# Query the trail
entries, total = await trail.query(content_id="civitai:image:12345")
# Check if already posted
if await trail.is_used("civitai:image:12345"):
print("Already posted, skipping")
# Get pipeline stats
stats = await trail.stats(requester="daily-post")
print(f"Posted: {stats['by_action'].get('posted', 0)}")
TypeScript
import { Trail } from "./trail";
const trail = new Trail("./data");
// Log an event
await trail.append("civitai:image:12345", "posted", "daily-post", {
details: { platform: "telegram", platform_id: "42" },
trace_id: "run-001",
});
// Query the trail
const { entries, total } = await trail.query({ content_id: "civitai:image:12345" });
// Check if already posted
if (await trail.isUsed("civitai:image:12345")) {
console.log("Already posted, skipping");
}
// Get pipeline stats
const stats = await trail.stats("daily-post");
console.log(`Posted: ${stats.by_action.posted ?? 0}`);
Entry Schema
{"version":2,"timestamp":"2026-04-05T14:07:00.123Z","content_id":"civitai:image:12345","action":"posted","requester":"daily-post","server":"telegram-mcp","trace_id":"run-001","details":{"platform":"telegram","platform_id":"42"}}
| Field | Type | Required | Description |
|---|---|---|---|
version |
int |
yes | Protocol version (2) |
timestamp |
string |
yes | ISO 8601 timestamp (UTC, milliseconds) |
content_id |
string |
yes | Content ID: source:type:id |
action |
string |
yes | Action performed (see below) |
requester |
string |
yes | Workflow/task ID that initiated this |
details |
object |
no | Platform-specific data with standard sub-fields |
trace_id |
string |
no | Groups entries across servers into one trace |
server |
string |
no | MCP server that wrote this entry (auto-set) |
entry_id |
string |
no | Unique entry identifier (for causality chains) |
caused_by |
string |
no | entry_id of the causing entry (maps to OTel parentSpanId) |
tags |
string[] |
no | Free-form labels for filtering |
Standard Actions
| Action | When |
|---|---|
fetched |
Content retrieved from source (candidate list) |
selected |
Chosen from candidates by the orchestrator |
posted |
Successfully published |
failed |
Publish attempt failed (details.error has structured error info) |
skipped |
Intentionally not used (details.reason explains why) |
retrying |
Scheduling retry after failure (details.attempt = attempt number) |
transformed |
Content modified (resize, transcode, translate) |
moderated |
Passed or failed moderation (details.result: "pass" / "reject") |
expired |
Content no longer eligible (TTL, source removed) |
delivered |
Delivery confirmed by platform (webhook, read receipt) |
delegated |
Task delegated to another agent/server (details.delegate_to, details.delegation_reason) |
received |
Content received from another agent/server (details.received_from) |
evaluated |
Quality/relevance scored (details.score: 0.0–1.0, details.evaluator) |
guarded |
Guardrail check (details.guardrail, details.passed, details.reason) |
acknowledged |
Human-in-the-loop approval (details.acknowledged_by, details.decision) |
Standard Tools
Every TRAIL-compatible MCP server exposes:
| Tool | Purpose |
|---|---|
get_trail(content_id?, action?, requester?, trace_id?, tags?, since?, limit?, offset?) |
Query the log with filters. Returns {entries, total} |
mark_trail(content_id, action, requester, details?, trace_id?, tags?) |
Write an entry explicitly |
get_trail_stats(requester?, since?) |
Summary statistics (counts by action, unique content IDs, date range) |
Publishing tools (send_photo, publish_post, etc.) accept optional content_id + requester + trace_id for automatic logging.
Standardized Details
TRAIL defines standard sub-fields in details for cross-server consistency:
{
"details": {
"platform": "telegram",
"platform_id": "42",
"url": "https://t.me/channel/42",
"error": {"type": "rate_limit", "message": "429", "retry_after": 60},
"cost": {"tokens_in": 150, "tokens_out": 50, "usd": 0.003},
"content": {"type": "image", "width": 1024, "height": 1024, "model": "Flux.1"},
"duration_ms": 1200,
"delegate_to": "optimizer-mcp",
"score": 0.92,
"evaluator": "llm-judge",
"guardrail": "nsfw-filter",
"passed": true,
"acknowledged_by": "[email protected]",
"decision": "approve"
}
}
See SPEC.md — Details Field for the full specification.
Deduplication
The orchestrator handles deduplication, not the servers:
1. Before fetching → get_trail(requester="daily-post", since="...") on source
2. Before posting → get_trail(content_id="civitai:image:12345", action="posted") on destination
3. If already posted → skip
Servers MAY offer convenience filters (e.g., exclude_used=true) that use the trail internally.
Discovery
Servers advertise TRAIL support via capabilities:
{
"capabilities": {
"trail": {
"version": 2,
"server": "telegram-mcp",
"conformance": "standard",
"actions": ["fetched", "selected", "posted", "failed", "skipped", "guarded"],
"auto_log_tools": ["send_photo", "send_message", "publish_post"],
"supports": {
"trace_id": true,
"entry_id": true,
"caused_by": true,
"tags": true,
"server_field": true
},
"retention_days": 90
}
}
}
Adopting TRAIL in Your MCP Server
Basic (Level 0) — deduplication and simple tracking:
Standard (Level 1) — production pipelines:
4. Set server name in Trail constructor
5. Add get_trail_stats tool
6. Add content_id + requester + trace_id params to publishing tools
7. Advertise TRAIL in capabilities with "conformance": "standard"
Full (Level 2) — multi-agent observability:
8. Enable entry_id auto-generation and caused_by support
9. Implement all 15 standard actions
10. Add OTel export capability
Full specification: SPEC.md | SPEC.ru.md
Why Not...
| Alternative | Why TRAIL is better for content tracking |
|---|---|
| Shared database | Creates coupling, deployment complexity, single point of failure. MCP servers are isolated by design. |
| Message queue | Overkill. The LLM orchestrator already mediates all servers — it IS the message bus. |
| OpenTelemetry | Traces tool calls, not content semantics. Doesn't know what was published where. TRAIL has an OTel bridge for combining both. |
| IETF AAT | Compliance-focused (hash chains, ECDSA signatures). TRAIL is developer-first — lightweight and zero-dependency. |
| Langfuse / LangSmith | LLM observability platforms — trace API calls, not content lifecycle. Require cloud/self-hosted backend. |
| Google A2A | Agent-to-agent communication protocol, not a content tracking log. Different layer. |
| Agent Protocol | Defines agent API, not a logging format. Tasks/Steps, not content semantics. |
| ActivityPub | Designed for social federation, not AI tool orchestration. Massive overhead. |
FAQ
Q: Why readable field names instead of short ones?
A: A protocol for decades must be self-documenting. content_id is clear on first sight. The overhead is ~15 KB/year at typical rates — negligible.
Q: Do I need all the optional fields?
A: No. The five required fields are the whole protocol. Optional fields unlock advanced features when you need them.
Q: Orchestrator crashes mid-pipeline?
A: Use trace_id to find all entries for that run. The last entry's action shows where to resume.
Q: What are conformance levels?
A: Three tiers — Basic (5 fields + 2 tools), Standard (+ tracing, server, auto-logging), Full (+ causality chains, all 15 actions, OTel export). Start at Basic.
Q: Multi-agent pipelines?
A: delegated/received action pairs + caused_by chains + server field. The orchestrator reconstructs the full DAG. See SPEC.md — Multi-Agent Patterns.
Prior Art
We searched extensively for existing solutions. As of April 2026, no cross-MCP content tracking protocol exists:
- MCP Spec — no server-to-server communication by design
- CA-MCP (arXiv 2601.11595) — shared context store for transient state, not persistent content logs
- lokryn/mcp-log — JSONL audit logging for operations (SOC2/HIPAA), not content tracking
- IBM ContextForge — gateway proxy with OTel tracing, not content semantics
- OpenTelemetry GenAI — semantic conventions for LLM calls (Development status), not content lifecycle
- IETF AAT (draft-sharif-agent-audit-trail) — compliance-focused audit trail with hash chains, no content semantics
- Google A2A — agent-to-agent communication protocol with traceability extension, not a logging format
- Langfuse / LangSmith / Arize Phoenix — LLM observability platforms, trace API calls not content
- Agent Protocol (agentprotocol.ai) — REST API for interacting with agents, not a log format
TRAIL fills a unique gap: lightweight, zero-dependency content tracking with multi-agent support — no other protocol combines content semantics (content_id), zero shared state, and agent patterns (delegation, evaluation, guardrails).
MCP Servers Implementing TRAIL
| Server | Description | Language |
|---|---|---|
| civitai-mcp-ultimate | Civitai API — models, images, videos, prompts | Python |
| telegram-api-mcp | Telegram Bot API v9.6 — full coverage | TypeScript |
Implementing TRAIL in your server? Open a PR to add it here.
Other Projects by @timoncool
| Project | Description |
|---|---|
| ACE-Step Studio | AI music studio — songs, vocals, covers, videos |
| VideoSOS | AI video production in the browser |
| Bulka | Live-coding music platform |
| GitLife | Your life in weeks — interactive calendar |
| ScreenSavy.com | Ambient screen generator |
Support the Author
I build open-source software and do AI research. Most of what I create is free and available to everyone. Your donations help me keep creating without worrying about where the next meal comes from =)
All donation methods | dalink.to/nerual_dreming | boosty.to/neuro_art
- BTC:
1E7dHL22RpyhJGVpcvKdbyZgksSYkYeEBC - ETH (ERC20):
0xb5db65adf478983186d4897ba92fe2c25c594a0c - USDT (TRC20):
TQST9Lp2TjK6FiVkn4fwfGUee7NmkxEE7C
Star History
MIT License · Made with Claude Code
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found