Canonical helpers for Stallari-conformant MCP servers — `_meta` audit envelope builder and per-record domain-hint pattern engine. Drop-in primitives for Python MCP authors targeting Stallari's contract surface (DD-333/DD-338).
Project description
stallari-mcp-helpers
Canonical helpers for Python MCP servers targeting Stallari's contract surface — a tight library that lets your tools emit the audit envelope and per-record domain attribution the Stallari assembler expects, without you rolling your own.
If you're porting a Python MCP server to Stallari and want first-party-tier conformance, this is the on-ramp.
What this is
Two small modules:
audit_envelope— renders the canonical_meta: {...}JSON-tail block the Stallari assembler lifts into itsContextPacket.provenanceaudit trail. Locked encoding: tight JSON separators, alphabetically-sortedfiltered_by, Unicode preserved, required/optional field discipline matching the DD-338 wire contract.domain_hint— pattern engine for per-record domain attribution when your tool spans multiple user-defined domains (e.g. a Gmail account that mixeswork-acmeandpersonal). Users author YAML rules in<state-root>/blade-config/<your-mcp>/config.yaml; your tool loads them once, callscompute_domain_hint(record, patterns)per record, and folds the result into_meta.domain_hints.
This package does not ship MCP-server scaffolding, tool registration, HTTP/IPC, inference, or stable-ordering primitives. It's deliberately small.
Audience
Direct consumers today:
- First-party Stallari Python MCP servers —
gmail-blade-mcp,home-assistant-blade-mcp,mastodon-blade-mcp,tailscale-blade-mcp,syncthing-blade-mcp,caldav-blade-mcp,fastmail-blade-mcp. (These previously each carried their own copy ofdomain_hint.py+ their own_meta-envelope helper; this package eliminates the duplication.)
Broader audience:
- Any Python MCP author who wants their server to dispatch through Stallari at first-party-tier conformance. You don't have to be on the Stallari team. Add the dep, use the helpers, declare your tool capabilities honestly in your pack manifest, and the assembler will treat your tool as a first-class participant.
If you're integrating a third-party MCP that you don't control, the Stallari adapter-transform layer (deterministic YAML, in-process) handles you separately — you don't need this library. See the Stallari docs for adapter-transform virtualisation if that's your path.
Install
uv add stallari-mcp-helpers
# or
pip install stallari-mcp-helpers
Quick start — emitting a _meta envelope
from stallari_mcp_helpers import meta_envelope, append_meta
def my_search_tool(query: str, scope: str = "personal") -> str:
records = upstream_api.search(query, filter=f"scope={scope}")
body = format_records(records)
meta = meta_envelope(
matched_total=records.total_matched,
returned=len(records.items),
latency_ms=records.latency_ms,
filtered_by=[f"scope={scope}", f"query={query}"],
# required fields:
redactions=[],
next_cursor=None,
# optional — omit when None/empty:
error_notes=None,
domain_hints=None,
)
return append_meta(body, meta)
Output (assembler-side regex contract: \n\n_meta: (\{.*\})$):
<your body>
_meta: {"matched_total":42,"returned":10,"latency_ms":234,"filtered_by":["query=foo","scope=personal"],"redactions":[],"next_cursor":null}
Quick start — per-record domain attribution
User authors <state-root>/blade-config/<your-mcp>/config.yaml:
patterns:
- field: from
op: contains
value: "@acme.com"
domain: work-acme
- field: labelIds
op: equals
value: Label_42
domain: personal
Your tool loads it once at startup:
from pathlib import Path
from stallari_mcp_helpers import load_patterns_from_yaml, compute_domain_hint, meta_envelope, append_meta
_PATTERNS_PATH = Path.home() / ".local/state/stallari/blade-config/my-mcp/config.yaml"
def _load_patterns():
try:
return load_patterns_from_yaml(_PATTERNS_PATH.read_text())
except (FileNotFoundError, OSError):
return [] # graceful degradation — Convention #22
_patterns = _load_patterns()
Then per request:
def my_list_tool() -> str:
records = upstream_api.list()
domain_hints = {
r.id: hint
for r in records
if (hint := compute_domain_hint(r.to_dict(), _patterns)) is not None
}
meta = meta_envelope(
matched_total=len(records),
returned=len(records),
latency_ms=elapsed,
filtered_by=[],
redactions=[],
next_cursor=None,
domain_hints=domain_hints or None, # omitted when empty
)
return append_meta(format_records(records), meta)
Manifest declarations you'll make
Per tool in your pack catalog entry:
{
"name": "my_search_tool",
"granularity": {
"scope_filtering": "server-side",
"deterministic_ordering": "stable",
"audit_surface": "structured",
"domain_scope": "multi"
}
}
Four honest declarations. If you implement scope_filtering: server-side you must actually filter at the upstream API (not in your code post-fetch). If you declare deterministic_ordering: stable your output must be reproducible. If you declare audit_surface: structured your tool must emit meta_envelope(...) on every call. If you declare domain_scope: multi your tool must emit per-record domain_hints when configured.
Stallari's conformance harness verifies these claims against your actual tool behaviour at pack-acceptance time. Honest degraded declarations always pass; lying about a capability fails. Start with the most conservative declarations and bump each axis as you implement the corresponding behaviour.
What you don't have to think about
- Exact JSON encoding of
_metaenvelopes (separator choice, sort order, Unicode handling) - How to handle
domain_hintswhen no patterns match - Graceful degradation on missing config files
- The assembler-side
ContextPacketshape that consumes your envelopes - Future contract evolution — the library version-pins the wire shape
All of that lives here. You write your tool, declare your contract honestly, and the rest is plumbing.
API reference
audit_envelope
def meta_envelope(
*,
matched_total: int,
returned: int,
latency_ms: int,
filtered_by: list[str] | None = None,
redactions: list[str] | None = None,
next_cursor: str | None = None,
error_notes: list[str] | None = None,
domain_hints: dict[str, str] | None = None,
) -> str:
"""Render the canonical `_meta: {...}` JSON-tail envelope line."""
def append_meta(body: str, meta_line: str) -> str:
"""Append a `_meta: {...}` line to a tool's body with `\\n\\n` joiner."""
domain_hint
@dataclass(frozen=True)
class Pattern:
field: str
op: Literal["equals", "contains", "glob"]
value: str
domain: str
def compute_domain_hint(
record: dict[str, Any],
patterns: list[Pattern],
) -> str | None:
"""First-match-wins. Returns matched domain or None."""
def load_patterns_from_yaml(yaml_str: str) -> list[Pattern]:
"""Parse a `patterns:` YAML block. Malformed/empty returns []."""
Versioning
SemVer. 0.x.y series while the public API stabilises; 1.0.0 once consumers have shipped against 0.x for >30 days with no API churn.
Breaking changes after 1.0.0 get a one-minor-version deprecation window (v1.y warns, v2.0 removes).
License
MIT. See LICENSE.
Contributing
This is internal Stallari plumbing during the 0.x series — issues + PRs accepted but the API surface is still settling. Submit issues at the GitHub tracker.
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 stallari_mcp_helpers-0.1.1.tar.gz.
File metadata
- Download URL: stallari_mcp_helpers-0.1.1.tar.gz
- Upload date:
- Size: 53.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cae3bc9197c1b474d61a0f40cff3be736ec3170bd589e4f908772dd1adff4541
|
|
| MD5 |
c7dbac1643a591b4cbb53d628b887cca
|
|
| BLAKE2b-256 |
209e5278b5587ac6f2c19a3323fc3a075c81e0bc14d406701cfdfba3a9cd7369
|
Provenance
The following attestation bundles were made for stallari_mcp_helpers-0.1.1.tar.gz:
Publisher:
publish.yml on Groupthink-dev/stallari-mcp-helpers
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stallari_mcp_helpers-0.1.1.tar.gz -
Subject digest:
cae3bc9197c1b474d61a0f40cff3be736ec3170bd589e4f908772dd1adff4541 - Sigstore transparency entry: 1616140228
- Sigstore integration time:
-
Permalink:
Groupthink-dev/stallari-mcp-helpers@cdfe4932af33d2fc4fa9c8c9d843016050909062 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Groupthink-dev
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cdfe4932af33d2fc4fa9c8c9d843016050909062 -
Trigger Event:
push
-
Statement type:
File details
Details for the file stallari_mcp_helpers-0.1.1-py3-none-any.whl.
File metadata
- Download URL: stallari_mcp_helpers-0.1.1-py3-none-any.whl
- Upload date:
- Size: 9.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fcd13c5b453c1685ec9ffa176a4ddde8f30d1f30ad8cce7acafaa94badf1ce73
|
|
| MD5 |
a44e9a292415eb7443273f6d8143ed6f
|
|
| BLAKE2b-256 |
337be3c6e60a7b8573a8159cf54476783e5c93049bdd2c5e60f4f932b87f1f76
|
Provenance
The following attestation bundles were made for stallari_mcp_helpers-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on Groupthink-dev/stallari-mcp-helpers
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stallari_mcp_helpers-0.1.1-py3-none-any.whl -
Subject digest:
fcd13c5b453c1685ec9ffa176a4ddde8f30d1f30ad8cce7acafaa94badf1ce73 - Sigstore transparency entry: 1616140253
- Sigstore integration time:
-
Permalink:
Groupthink-dev/stallari-mcp-helpers@cdfe4932af33d2fc4fa9c8c9d843016050909062 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Groupthink-dev
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cdfe4932af33d2fc4fa9c8c9d843016050909062 -
Trigger Event:
push
-
Statement type: