Skip to main content

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 its ContextPacket.provenance audit trail. Locked encoding: tight JSON separators, alphabetically-sorted filtered_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 mixes work-acme and personal). Users author YAML rules in <state-root>/blade-config/<your-mcp>/config.yaml; your tool loads them once, calls compute_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 serversgmail-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 of domain_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 _meta envelopes (separator choice, sort order, Unicode handling)
  • How to handle domain_hints when no patterns match
  • Graceful degradation on missing config files
  • The assembler-side ContextPacket shape 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


Download files

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

Source Distribution

stallari_mcp_helpers-0.1.0.tar.gz (53.3 kB view details)

Uploaded Source

Built Distribution

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

stallari_mcp_helpers-0.1.0-py3-none-any.whl (9.6 kB view details)

Uploaded Python 3

File details

Details for the file stallari_mcp_helpers-0.1.0.tar.gz.

File metadata

  • Download URL: stallari_mcp_helpers-0.1.0.tar.gz
  • Upload date:
  • Size: 53.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for stallari_mcp_helpers-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1d6f587a38279c9eaef1975cd9ad1984e98e9b820ae8ac75c9321fd85dd15c89
MD5 0a921bf4302c7cb7771f77326eb436e0
BLAKE2b-256 f4c60fc259a84c5d7bde39c0bcfdaf17198a37a02125f5b76465796b375ca8db

See more details on using hashes here.

Provenance

The following attestation bundles were made for stallari_mcp_helpers-0.1.0.tar.gz:

Publisher: publish.yml on Groupthink-dev/stallari-mcp-helpers

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stallari_mcp_helpers-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for stallari_mcp_helpers-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a82d3615c338db4498b19c784599a3c5ff966046b25823f9b9fbdf9a802c66b9
MD5 7dc0d0d2335b95b8386ad4a91d318ce5
BLAKE2b-256 4fe30e5bbbec5958b51a698f971cef7b8df7f18651579f01cd89b9dcbc96412f

See more details on using hashes here.

Provenance

The following attestation bundles were made for stallari_mcp_helpers-0.1.0-py3-none-any.whl:

Publisher: publish.yml on Groupthink-dev/stallari-mcp-helpers

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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