Fresh import of Groupthink-dev/stallari-mcp-helpers-swift under the DD-450 freeze; NOT yet cut over. Pending metadata/ref verification and promotion to Stallari/mcp-helpers-swift.
Find a file
Piers 180533df0c
feat: DD-386 helper-lib hardening — _meta 12-key parity + lint body resolution + transport policy (v0.4.0)
AUD-04-12: domainHints added (12-key parity with the Python canonical);
collation locked to raw Unicode code-point order via public codePointLess
(Swift's default String < is Unicode-canonical — divergent); golden parity
fixture byte-locked vs the Python + TS siblings.

AUD-04-13: canonicalEmitNames reduced to ["appendMeta"] — makeResultWithMeta
is a per-blade wrapper, not a library symbol; bare-name trust let drifted
wrappers lint structured. Wrappers now satisfy S-AUD-001 only via resolved
bodies (free functions + new typeMethodsIndex method fallback); unresolvable
switch-dispatch handlers yield indeterminate, never a false over-declared.

AUD-04-08 class: new Transport.swift — token-absent HTTP refuses to serve,
wildcard binds refused unconditionally, strict-bool non-loopback opt-in,
CryptoKit constant-time validateBearer (websocket-upgrade enforcement doc).

79 tests / 6 suites green (per-suite run confirmed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 04:09:39 +10:00
.github/workflows fix(ci): drop macos-14 from matrix; Swift 5.10 incompat with tools-version 6.0 2026-05-24 11:01:21 +10:00
Sources feat: DD-386 helper-lib hardening — _meta 12-key parity + lint body resolution + transport policy (v0.4.0) 2026-06-12 04:09:39 +10:00
Tests/MCPHelpersTests feat: DD-386 helper-lib hardening — _meta 12-key parity + lint body resolution + transport policy (v0.4.0) 2026-06-12 04:09:39 +10:00
.gitignore Initial scaffold — DD-338 Phase E.swift substrate 2026-05-24 10:53:01 +10:00
CHANGELOG.md feat: DD-386 helper-lib hardening — _meta 12-key parity + lint body resolution + transport policy (v0.4.0) 2026-06-12 04:09:39 +10:00
LICENSE Initial scaffold — DD-338 Phase E.swift substrate 2026-05-24 10:53:01 +10:00
Package.swift feat: DD-338 Phase B — S-AUD-001 lint substrate (Swift) 2026-05-24 15:17:30 +10:00
README.md feat: DD-386 helper-lib hardening — _meta 12-key parity + lint body resolution + transport policy (v0.4.0) 2026-06-12 04:09:39 +10:00

stallari-mcp-helpers (Swift)

Canonical helpers for Swift MCP servers targeting Stallari's contract surface — a tight Swift Package that lets your tools emit the _meta audit envelope the Stallari assembler expects, without you rolling your own.

If you're porting a Swift MCP server to Stallari and want first-party-tier conformance, this is the on-ramp.

About Stallari

Stallari is an agentic personal-knowledge-management platform. It runs MCP servers as its tool surface — the way it talks to email providers, calendars, smart-home hubs, source control, cloud infra, etc. Your MCP server dispatches through Stallari's assembler, which audits each tool call against a wire-shape contract before lifting the result into the LLM's context. This package provides the canonical helpers that make a Swift MCP server emit that contract cleanly.

Sister packages

Language Package Source
Python stallari-mcp-helpers on PyPI stallari-mcp-helpers
TypeScript stallari-mcp-helpers on npm stallari-mcp-helpers-ts
Swift MCPHelpers via Swift Package Manager this repo

All three packages stay in lockstep on the wire shape — meta_envelope(...) in Python, formatMetaLine(...) in TypeScript, and formatMetaLine(...) in Swift all emit byte-equivalent _meta envelopes for the same input.

What this is

One small module:

  • MCPHelpers — renders the canonical _meta: {...} JSON-tail block the Stallari assembler lifts into its ContextPacket.provenance audit trail. Locked encoding: tight JSON separators, code-point-sorted filtered_by and domain_hints keys (raw Unicode scalar order, no normalisation — codePointLess, NOT Swift's default String <), required/optional field discipline matching the DD-338 wire contract (12 keys as of v0.4.0).
  • Plus the canonical HTTP transport policy (Transport.swift, v0.4.0 / AUD-04-08) — resolveHTTPTransport (refuse-to-serve when the token is absent, wildcard binds never permitted, non-loopback requires an exact-"true" opt-in) and validateBearer (constant-time bearer comparison; enforce on WebSocket upgrades too).

This package does not ship MCP-server scaffolding, tool registration, an HTTP server, inference, or domain-attribution primitives. It's deliberately small.

Audience

Direct consumers (future, post Stallari W5 Swift wave):

  • First-party Stallari Swift MCP servers — apple-mail-blade-mcp, apple-notes-blade-mcp, and the Swift surface of stallari-vault served as stallari-blade-mcp via the daemon. These don't yet ship the _meta envelope; this package lands ahead of W5 so the wave can adopt natively.

Broader audience:

  • Any Swift 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 SPM dependency, 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.

Install (Swift Package Manager)

Add to your Package.swift:

dependencies: [
    .package(url: "https://github.com/Groupthink-dev/stallari-mcp-helpers-swift", from: "0.1.0"),
],
targets: [
    .target(
        name: "MyBlade",
        dependencies: [
            .product(name: "MCPHelpers", package: "stallari-mcp-helpers-swift"),
        ]
    ),
]

Requires Swift 6.0+, macOS 14+ or iOS 17+.

Quick start — emitting a _meta envelope

import MCPHelpers

func mySearchTool(query: String, scope: String = "personal") async throws -> String {
    let records = try await upstreamAPI.search(query, filter: "scope=\(scope)")
    let body = formatRecords(records)

    let meta = MetaEnvelope(
        matchedTotal: records.totalMatched,
        returned: records.items.count,
        latencyMs: records.latencyMs,
        filteredBy: ["scope=\(scope)", "query=\(query)"],
        redactions: [],         // required, empty allowed
        nextCursor: nil          // required, nil allowed
    )
    return appendMeta(body, try formatMetaLine(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}

JSON keys are emitted with snake-case names (matched_total, latency_ms, filtered_by, next_cursor) for byte-parity with the Python and TypeScript sister packages — Swift idiomatic camelCase on the API surface, canonical snake-case on the wire.

Your manifest

Per tool in your pack catalog entry:

{
  "name": "my_search_tool",
  "granularity": {
    "scope_filtering": "server-side",
    "deterministic_ordering": "stable",
    "audit_surface": "structured",
    "domain_scope": "single"
  }
}

Four honest declarations. If you implement scope_filtering: server-side you must actually filter at the upstream API. If you declare deterministic_ordering: stable your output must be reproducible. If you declare audit_surface: structured your tool must emit formatMetaLine(...) on every call.

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)
  • Field-presence rules (required vs optional)
  • Whether to round latencyMs
  • The assembler-side ContextPacket shape that consumes your envelopes
  • Future contract evolution — the library version-pins the wire shape; the Python and TypeScript sisters stay in lockstep

API reference

public struct MetaEnvelope: Sendable, Equatable, Codable {
    public let matchedTotal: Int?            // optional since D.1, omit-when-nil
    public let returned: Int?                // optional since D.1, omit-when-nil
    public let latencyMs: Int
    public let filteredBy: [String]          // code-point sorted in output
    public let redactions: [String]          // required, defaults to []
    public let nextCursor: String?           // required, nil emits JSON null
    public let rowsAffected: Int?            // write-tier, omit-when-nil
    public let targetId: String?             // write-tier, omit-when-nil
    public let writeDurability: String?      // "edge" | "central" | "replicated"
    public let responseTimestamp: String?    // write-tier, omit-when-nil
    public let errorNotes: [String]?         // omitted when nil or empty
    public let domainHints: [String: String]? // v0.4.0; omitted when nil or
                                              // empty; keys code-point sorted

    public init(
        matchedTotal: Int? = nil,
        returned: Int? = nil,
        latencyMs: Int,
        filteredBy: [String] = [],
        redactions: [String] = [],
        nextCursor: String? = nil,
        rowsAffected: Int? = nil,
        targetId: String? = nil,
        writeDurability: String? = nil,
        responseTimestamp: String? = nil,
        errorNotes: [String]? = nil,
        domainHints: [String: String]? = nil
    )
}

public func formatMetaLine(_ meta: MetaEnvelope) throws -> String
public func appendMeta(_ payload: String, _ metaLine: String) -> String

// Locked cross-language collation (raw Unicode code-point order, no
// normalisation — Swift's default `String <` diverges):
public func codePointLess(_ lhs: String, _ rhs: String) -> Bool

// HTTP transport policy (v0.4.0, AUD-04-08 / AUD-04-31):
public enum TransportPolicyError: Error, Equatable, CustomStringConvertible
public struct HTTPTransportConfig: Sendable, Equatable {
    public let host: String
    public let port: Int
    public let token: String
}
public func strictEnvBool(_ value: String?) -> Bool   // true ONLY for "true"
public func resolveHTTPTransport(
    envPrefix: String,
    defaultPort: Int,
    environment: [String: String]? = nil,
    tokenVariable: String? = nil
) throws -> HTTPTransportConfig
public func validateBearer(authorizationHeader: String?, expectedToken: String) -> Bool

Locked 12-key _meta emission order (v0.4.0): matched_total, returned, filtered_by, latency_ms, redactions, next_cursor, rows_affected, target_id, write_durability, response_timestamp, error_notes, domain_hints.

Versioning

SemVer. 0.x.y series while the public API stabilises; 1.0.0 once consumer blade-mcps 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 annotates @available(deprecated), 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.