Skip to content
Buy · $14.99 Opens the GitHub repository in a new tab. The signed build is a separate download; see the README for what is in each.
Changelog

Bastion 1.12.0

Version 1.12.0 ·

Added

  • The iOS Simulator joins the catalog. Thirty-four entries now, twenty-three children and eleven remote. It drives a booted simulator — screenshots, the accessibility tree, taps, swipes, typing, app lifecycle and the staged environment — and installs from @mgcrea/mcp-ios-simulator.

    Two lanes reach it and they fail independently. xcrun simctl covers app lifecycle, the staged environment and the screen itself; only the accessibility tree and synthetic touches go through a WebDriverAgent runner, reached over the HOST's own loopback because a simulator shares the host network stack. That split is the real difference from the iOS Device server, where seeing anything at all requires the runner: here simctl io screenshot needs none, so everything except ui_tree works before one has ever started — and once writes are on, the server can start the runner itself. ios_simulator_diagnostics reports the two lanes separately.

    It takes no credentials, so it has no auth modes, and unlike the device server there is barely any setup either: a simulator needs no pairing and no Developer Mode toggle, so there is nothing for a profile to hold. IOS_SIMULATOR_ALLOW_WRITES gates the fourteen tools that actually drive it.

    The package itself defaults writes ON, the only entry here that does, reasoning that a phone belongs to a real person while a simulator is disposable and holds nobody's data. That default is never reached under Bastion, which writes the gate value explicitly on every spawn: the profile toggle is what decides, and a profile with writes off spawns a server that has not registered the driving tools at all.

  • Catalog entries say which packages npm can tie back to the repository they link to. A provenance badge now sits beside the package name in a server's pane and in the catalog list, and the website marks the same entries. Sixteen of the twenty-three published packages carry a SLSA build attestation from GitHub Actions; the seven that do not are not suspect, they publish the older way, so the badge is only ever shown and never negated.

    The claim is deliberately narrower than "attested". A bare attestation says some CI somewhere built the tarball, which is a thing a typosquat of a popular package can have too. What is checked is that the workflow's repository matches the docsUrl the entry already advertises, so the badge means the bytes trace to the source the reader can go and look at. All sixteen match today, which makes the check a drift detector rather than a one-off audit.

    It is a smaller claim than a review, and it is placed to say so: under the paragraph in a third-party server's Package card that finishes explaining nobody here read the code. Provenance ties a package to a source; it does not vouch for what is in it.

    make provenance prints what the registry currently holds, and make provenance-check fails when a claim in servers.json no longer does. Neither runs in CI, and neither belongs to the generator: it has to stay offline and deterministic because servers-check is a drift gate, and somebody else publishing overnight must not turn an unrelated pull request red. The two directions of drift are not treated alike — a stale true is a hard failure, because Bastion must never claim provenance it cannot show to someone deciding whether to run code on their machine, while a stale false is only advice, because a third party improving their release process is good news and good news must not fail a build.

    list_catalog returns the flag too, so a model choosing between two servers that do the same job has one trust signal it can actually act on.

Changed

  • A listing too small to be worth searching is no longer fronted, whatever the switch says. Loading tools on demand now resolves a third term beside the server's switch and the client's own deferral, and this one is measured rather than configured: a listing is fronted only when it has at least twice as many tools as the facade would send in its place, and costs at least twice as many tokens. Both have to hold.

    The count is the term that decides the real cases, and it is not a proxy for the bytes. What this feature sells is not compression, it is selection — eighty-five schemas go unsent because an agent needed two of them. A server exposing three tools offers no selection to make, so the index costs two round trips to learn what one listing already said.

    That is most of the remote catalog's shape. Cloudflare's hosted endpoint exposes search, execute and docs; Stripe ships a read and a write dispatcher beside its own search. They are already this design, and their listings are not small in bytes — Cloudflare's three descriptions measure about 1.7k tokens — so a floor counting bytes alone would front them and buy nothing. Nor can Bastion recover what a vendor's dispatcher already took away: the real tool name upstream is execute, so the audit row says execute either way, and the one advantage of doing this in the gateway does not apply.

    Measured rather than listed, deliberately. A set of vendors known to front their own tools would rot the first time one unpacked its dispatcher, and would do nothing for the small child server with the same problem and no vendor to name. Where the floor holds, the server's card says which half held instead of rendering a saving of nothing, the client's context bill counts the real listing, and get_server reports it as lazy_tools_note. It governs what is ADVERTISED only — a client still holding a fronted list goes on calling through it, which is the rule a pre-toggle tool name has always followed.