bastion
Allows MCP clients to use a shared Keycloak MCP server for identity and access management, with credentials managed per profile.
Allows MCP clients to use a shared Shopify MCP server for store operations, with credentials stored per profile in the Keychain.
Integrates Stripe's own MCP server, supporting per-profile OAuth sign-in and restricted API keys, so clients can interact with Stripe without handling tokens directly.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@bastionshow me the recent tool calls from the shopify prod server"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Bastion
One MCP server, running once, for every client on the machine.
A macOS menu bar app that supervises your MCP servers instead of letting each editor spawn its own: one process per server, credentials in the Keychain, and every tool call recorded.
Shipping. Signed, notarized, and sold direct at bastion.mgcrea.io — download the current release, or buy a licence for $14.99 (€14.99 in the EU). A 30-minute in-memory trial runs the whole app. The source is public and stays public; what is sold is the build. Status lists exactly what runs today, and CHANGELOG.md what each release changed.
The problem
Every MCP client spawns its own copy of every stdio server. Open three projects in Claude Code and
you have three mcp-shopify processes, three credential sets in three environments, no logs, and no
way to see what tool was called with what argument. Nothing is auditable and nothing is shared.
The credentials are the sharp end. Right now mcp-shopify, mcp-keycloak, mcp-tastytrade and
mcp-appstore-connect each keep a .mcp.json holding real secrets in plaintext — readable by
anything running as you, and one careless git add from being published. A brokerage refresh token
sits in a file beside the code.
Bastion runs each server once, holds its credentials in the Keychain, and fronts it over loopback HTTP so several clients share one instance.
Related MCP server: mcp-computer-use
How it works
Each client points at a URL instead of a command:
http://127.0.0.1:8720/s/<profile>/<server>A profile is a named credential and configuration set — prod/shopify, staging/shopify,
rgis/keycloak. It is the answer to the obvious objection to a shared gateway: one global instance
is one identity, and one identity is unusable. Every repo in mgcrea-ai already carries different
credentials for the same server, so a true singleton would be unusable for its own author on day
one.
Bastion speaks HTTP to the clients and stdio to the child. The MCP handshake happens once, at
spawn — every client's initialize is answered from that one result, and each client's request ids
are rewritten into the supervisor's own numbering and back again, so two clients using id 1 at the
same time cannot receive each other's answers.
One process, N clients
This inverts cupertino's design, which maps one connection to one process on purpose. Three reasons are given there for that choice, and each one has to be answered rather than waved at:
Cupertino's reason | Bastion's answer |
state | The handshake happens once, at spawn, and is replayed to every client. The 2026-07-28 spec went stateless-first, which is what makes this correct rather than a hack. |
crashes | Backoff, a circuit breaker, and the blast radius said out loud: when a child dies every client waiting on it is told, with a count. |
write permissions | Per profile, not per process. |
What is genuinely given up is isolation between two clients of the same profile. That is the trade, and it is the right one: they are the same identity with the same permissions, which is exactly the case where a second process bought nothing but memory.
For a remote server none of this applies, because there is no process to share. Three of the four reasons to put a gateway in front of a server survive — the Keychain, the audit line, and per-profile identity — and one does not. The write gate is the one that weakens: a child gets an environment variable that switches its destructive tools off inside the server, and a remote server has no environment, so the gate becomes a list of tool names Bastion refuses to forward. That filters Bastion, not the server. Anyone holding the credential can call the same API directly, and the credential's own scopes are the real boundary — which is why the Stripe entry says to use a restricted key.
Servers
Bastion ships with nothing installed. It ships with a catalog of thirty-three, listed in
servers.json and documented in docs/servers.md; the list an
install actually runs lives in Application Support, starts empty, and the user edits it. Install
from the catalog, or add any other MCP server by npm package name. Code is fetched on demand into
Bastion's own directory and run with the Node runtime in the app — the bundle carries node and npm
and no servers at all.
The transport, its rules, and everything measured rather than assumed about it are in docs/remote-servers.md.
For a remote server there is a second way to authenticate, and it is the one worth having. Bastion runs the OAuth dance once, per profile, and every client shares the result without ever seeing a token — nothing is typed into a config file, and access is revoked from the provider's own dashboard rather than hunted for across four repos. Press Sign in with Stripe on a profile; Bastion discovers the authorization server from the 401, registers itself dynamically, runs PKCE S256 in an ephemeral browser window, and keeps the token in the Keychain, refreshing it behind every client's back. A restricted key still works and is not going away — it is the only thing that works for a Connect platform acting on a connected account, where Stripe does not support OAuth.
One entry in the catalog is not a package. Stripe runs its own MCP server, and Bastion fronts
that rather than shipping a client for it — the entry used to be a placeholder for
@mgcrea/mcp-stripe, a package nobody ever wrote. This is the same position the catalog already
takes: what is worth building is the runtime underneath a server, not another server. A remote
entry has no package, no process and nothing to install; what it still has is the credential in the
Keychain instead of in every repo's .mcp.json, one identity per profile, and every call in the
audit log. What it does not have is the headline — see Status.
What is still closed is how a request selects one, and that is the half that was ever a security property. The child inherits the user's credentials and runs unsandboxed, so "run whatever the config names" is the same shape of hole as CVE-2025-49596. A client names a profile and a server id; the id resolves against the list the user installed, or it 404s. Nothing arriving over the wire can name a package, a path or an argv, and a custom entry supplies a package and a bin name rather than a command line.
Bastion curates lightly, and only to fill the first screen. The catalog seeds thirty-three entries — eleven servers written here, eleven somebody else publishes, and eleven endpoints their own vendors operate — because a catalog that opens with nothing recognisable in it teaches nobody what the app is for. The middle group is named rather than folded into the first: Bastion installs those from npm and runs them on your machine with a profile's credentials in their environment, and what it adds to them is the supervision, the Keychain and the audit line, not a review of the code. The app says so on each one. It is still not trying to be a directory: Docker MCP Toolkit ships hundreds of curated servers, Anthropic ships MCPB double-click install and an official registry, and anything not seeded here is one npm package name or one URL away. The part worth building is the runtime underneath.
Adding an entry to the catalog is a manifest edit and make servers; every generated copy is
checked in CI.
Bastion, as one of its own servers
Every other entry in the list is an npm package Bastion downloads and relays to. One is the app.
bastion answers MCP in-process — no package, no child, no credentials of its own — and its tools
are the window: list and describe servers, install one from the catalog or add any npm package,
keep an installed one up to date, create profiles, put credentials in the Keychain, wire clients,
and read what is running.
Updating goes through the same two steps the server pane does, and in the same order.
check_server_update asks npm what it would install and changes nothing; update_server resolves
the package at latest again and re-downloads it, which is equally the retry for a download that
failed and the repair for a dependency tree npm would rebuild. Neither runs on a timer — Bastion
reaches the registry when something asks it to and not otherwise — so what get_server reports
under update is only ever as fresh as the last time somebody asked, and it says unchecked
rather than guessing.
It is the one server that cannot be removed. Removing a server takes its profiles, their Keychain entries and its downloaded code with it, which was far too much to mean "not right now" — so every server now has a switch instead. A disabled server stops its children and refuses requests with its own sentence; its profiles, credentials and installed code are untouched, and client configs are not rewritten behind your back.
Three rules hold whatever the profile's write gate says, and they are the reason this is safe to ship:
No tool returns a secret.
set_credentialhas no counterpart, andlist_profilessays which variables are set, never what to.It cannot switch itself off, which would leave no way to switch it back on from there.
It cannot delete itself.
Everything that changes anything sits behind the same per-profile write gate as every other server:
with Allow writes off, the mutating tools are absent from tools/list entirely rather than
offered and refused, so a model never plans around a tool it cannot use. And every call it serves
goes through the same audit line as a relayed one — the server that can change everything is not
the server that leaves no trace.
It ships disabled. It is a control plane for a daemon holding every credential you own, so it takes three deliberate acts to reach: switch it on, give it a profile, wire a client.
The licence gate does not apply to it, and that is the gate's own rule rather than a hole in it: what is sold is the relay. This server relays nothing, spawns nothing and holds no credential. It also means an unlicensed user can have their agent set Bastion up, and meets the licence sentence at the point they have something to lose by not having one.
make builtin # the write gate, the two self-refusals, and the secrets wallWhat the audit log sees, and what it does not
Bastion sees the JSON-RPC frames crossing the gateway: which profile, which method, which tool, and the arguments it was called with. A profile can opt into recording what came back as well; that is off by default, because a result is the unbounded half.
A credential is never recorded. set_credential takes a secret as an argument, so its arguments
are dropped whatever the setting says, and any value under a key the server's manifest marks secret
is blanked. That is a rule in one place — CallCapture — rather than a habit at three call sites,
and make builtin plants a canary through set_credential and asserts it never comes back out.
Nothing recorded is written to disk unless you ask for it. By default the log is a bounded ring
in memory, cleared when Bastion quits. This is not a matter of intent: every ordinary log line is
mirrored to stderr, which for an app started by LaunchServices outlives the process, so payloads
take a separate path that never reaches it. make builtin asserts that too, against the real
bundle.
Settings › Activity turns on a durable audit log: append-only JSONL under Application Support, 0600, in segments, with retention by age and size. Whether that file carries arguments and results is a second switch, off on its own — keeping a record of which tools ran is a smaller thing to leave on disk than keeping what they were called with.
Each record carries a hash of the one before it, so an edited field, a removed record or a truncated file can be detected. That is the whole claim, and it is worth being exact about: it catches tampering by something that does not know it is a chain, and corruption by something that was not trying. It is not proof against anyone who can write the file, because they can recompute it. Retention drops whole segments for the same reason — a chain cannot lose a record from the middle and still verify.
Export writes the segments plus a manifest.json naming each one, its record count and its digest,
with the chain head. The count matters: a chain cannot detect its own truncation, because lopping
off the tail leaves a shorter valid chain. Signing is optional and goes in signature.json beside
the manifest rather than inside it — a signature written into the bytes it signs is what makes half
the signed-JSON formats in the world ambiguous. A signature proves the export came from this Mac and
was not altered afterwards; it does not prove the log was not curated before it was signed, and it
means nothing to a recipient who has not been given the key some other way.
An agent asking Bastion for recent activity is answered with its own profile's lines — which it already sent and received, so it learns nothing it did not have. Another profile's lines never carry arguments or results.
It does not see what a server then does over the network or on the filesystem. A server that reads a file it was never asked about does so out of Bastion's sight. This is a record of requests, not a sandbox, and the website has to say so in these words too.
Security
A loopback daemon holding every credential you own is the exact shape of CVE-2025-49596 — Anthropic's own MCP Inspector, where a listener plus no CSRF protection meant a page you visited could reach it and execute code — plus the rust-sdk and FastMCP DNS-rebinding advisories, whose root cause was "no rebinding protection by default, because it is only localhost".
Five rules, in the first commit that opened a socket rather than a hardening pass afterwards:
Bind
127.0.0.1explicitly. Never0.0.0.0, and not configurable.Validate
Originon every request.Validate
Hoston every request — this is the anti-rebinding rule.A per-client bearer token, minted at wiring time, kept in the Keychain.
Secrets never written to any config file. The token goes in the client config; the credential stays in the Keychain. What leaks if a
.mcp.jsonleaks is a revocable loopback token instead of a Shopify secret.
The order of 2, 3 and 4 is load-bearing: a rebinding attempt must be refused with 403 whether or not
it also guessed a token, or the 401 tells an attacker their Host was accepted.
A remote server adds a sixth, and it is the same rule pointed outward. The five above constrain what
may reach Bastion; a URL in the installed list constrains where Bastion may reach, and it is the
analogue of a command line — fetch(whatever_you_typed) beside spawn(whatever_you_typed).
A remote endpoint is https to a public host, checked on every request rather than once when it was added, because a name that passed yesterday can resolve somewhere else today. Loopback, private, link-local and the cloud metadata address are refused; so is Bastion's own gateway, which is the sharp one — a "remote server" pointed at
127.0.0.1:8720would be a way to replay one client's bearer token against every other profile in the app. A cross-origin redirect is refused rather than followed without the credential.
make auditscripts/audit-listener.sh launches the built bundle and asserts all of
it: loopback-only via lsof, a foreign Origin and a rebound Host both refused, the refusal
order, unauthenticated and wrong-token requests refused, and that profiles.json holds no value for
any variable the manifest marks secret. It replaces cupertino's audit-network.sh, whose claim — no
network at all — Bastion cannot make. The property worth keeping was "a claim CI can check", not the
particular claim.
scripts/remote-check.swift asserts rule 6 as a pure function of a
URL — 45 checks, no app and no network — and make remote-live-check asserts the whole path against
Stripe's real server, which needs no credential because an unauthenticated initialize is answered
with a 401 that exercises every step but the last.
The app ships with no entitlements file at all. Spawning children and binding loopback need
none, and with the sandbox off com.apple.security.network.server is unnecessary. An empty
permission set that is true by construction is checkable; one arrived at by deletion is not, so the
audit asserts the setting is absent rather than empty.
Status
Built and verified:
Gateway | loopback HTTP, |
Supervisor | one child per profile, id remapping, backoff, circuit breaker, idle stop |
Dialect | dual-era: modern 2026-07-28 and legacy |
Catalog | thirty-three seeded servers, a generator, and a CI drift check |
Server store | the user's own list, on-demand npm install, add, remove, and a per-server switch |
Remote servers | an https endpoint fronted like any other server — eleven of them in the catalog |
OAuth 2.1 | discovery, dynamic registration, PKCE and refresh — one consent, every client |
Bastion's server | Bastion as one of its own servers, so an agent can manage it — off by default |
Keychain | per-profile credentials, per-client tokens |
Activity window | what is running, who is attached, and every tool call with its arguments, live |
| stdio hosts reach the gateway over HTTP; starts Bastion if it is not up |
Migration | four |
| four concurrent clients, colliding ids, exactly one child, |
| the five security rules, against the real bundle |
| 24 conformance checks across both eras |
| the write gate hides the write tools, and no tool returns a secret |
| dialect, HTTP parser, streamed replies, call capture and the audit chain — 513 checks |
| an export signature through a round trip, key loss included — 18 checks |
| where a remote server may live, the SSE reader, and the OAuth client — 100 checks |
Bastion is what the 2026-07-28 spec calls a dual-era server. A modern client declares its
protocol version, identity and capabilities in each request's _meta and needs no handshake at
all; a legacy client opens with initialize and is served that way. Both land on the one handshake
Bastion took with the child at spawn, and server/discover — mandatory in the modern revision, and
implemented by none of these servers — is synthesised from it.
None of the thirty-three catalog servers are modern. The eleven children written here run an SDK whose
newest protocol is 2025-11-25, which is what they negotiate. The manifest said 2025-06-18 until a
live handshake was actually run against one; that was Bastion's own pin masquerading as a fact about
the servers. A server you add yourself is fronted the same way, and declares its own dialect when
you add it.
The eleven third-party children were measured, not seeded, and measuring them took asking the
right question. A handshake that requests 2025-06-18 gets 2025-06-18 back from every one of
them, which measures nothing; asking for 2026-07-28, a revision none of them supports, makes each
one answer with its own newest instead. All eleven say 2025-11-25. Two of them, DBHub and
Context7, build on the 2.0 SDK that knows 2026-07-28 and still negotiate legacy — the newest
revision an SDK knows is not the one a server speaks, which is the whole reason this is measured
rather than read off a dependency.
Ten of the eleven remote entries carry a seeded dialect, not a measured one. They all refuse
initialize without a credential, so 2025-11-25 is a starting point in the manifest and the first
real handshake is what will measure them — RemoteInstance logs dialect drift and Activity shows
what was actually negotiated. Cloudflare Docs is the exception: it answers unauthenticated, and was
measured at 2025-11-25. Vercel advertises 2026-07-28, which would make it the first modern server
in the file, but proposing a version a server does not take fails the connection, so that number
goes in after somebody measures it and not before.
Stripe's is the oldest dialect in the file, and it took a credential to find out.
mcp.stripe.com refuses initialize without one — it answers 401 with a WWW-Authenticate naming
its protected-resource metadata — so for a while the entry carried the default an unmeasured server
gets. A live handshake negotiates 2025-03-26, two revisions behind that default, which is exactly
why the default is never left in place: the manifest would have claimed a version this server does
not speak, and nothing would have looked wrong.
Not built yet: a Homebrew cask, and a login item — which is what would let a type: http client
reach Bastion from cold the way a bridge-spawning one already can. The signed release path itself
(Developer ID signing, notarization, Sparkle and the appcast) has shipped since 1.0.0; see
Working on it.
Seven limitations worth knowing now:
Bastion has no login item yet. A stdio client's bridge starts it on demand, so a Claude Desktop entry works from cold. A client configured with a plain
type: httpURL has no such path and needs Bastion already up — which is the case for the four repos below.The repointed repos need Bastion running.
mgcrea-ai/mcp-{shopify,keycloak,appstore-connect}/.mcp.jsonnow callhttp://127.0.0.1:8720/...instead of spawning anything, so with Bastion stopped those servers are simply unreachable. There is no login item yet; that lands with the release path.A remote server's write gate is a filter, not a boundary. A child gets an environment variable that switches its destructive tools off inside the server. A remote server has no environment, so the gate becomes a list of tool names Bastion will not forward — plus any tool the server annotates as not read-only. Anyone holding the credential can call the same API directly, so the credential's own scopes are the real boundary. For Stripe that means a restricted key, which is what its catalog entry says.
A remote server's rate limit is shared. With one process per client each client spent its own budget upstream. Behind one profile they spend one, so a client in a loop can exhaust the limit for every other client of that profile. That is what sharing an identity means, and there is no fix for it at this layer.
Server-initiated requests are refused, not routed. Sampling, elicitation and roots get a JSON-RPC error explaining why: a shared instance has no single client to ask, and picking one would hand one project's agent a prompt raised on behalf of another's. The 2026-07-28 spec replaces all three with Multi Round-Trip Requests and puts them on a ~12-month offramp. No server in the catalog uses any of the three, so the full MRTR resume path is unbuilt rather than broken — though a server somebody adds could use one, which is the first thing the open list makes reachable that the closed one did not.
subscriptions/listenandlist_changedhave nowhere to go. A POST that asked for progress is now answered with an SSE stream, sonotifications/progressfrom a child reaches the client that asked for it — a request must carry aprogressTokenand anAccept: text/event-stream, and anything else is answered exactly as before. That is a per-REQUEST channel, though, and the two remaining gaps need a per-client one: alist_changednames no request, so on a shared instance there is no client it belongs to, andsubscriptions/listenneeds a channel outliving a request, which is the GET stream the 2026-07-28 revision removed. Both are optional in the spec, and both are still real gaps. A remote server's stream is also still collapsed rather than forwarded, for a reason that is not about routing:RemoteEndpoint.verifyrefuses a rebinding answer only because the body is buffered until the addresses are known.Mcp-Param-*headers are forwarded but not validated. Doing it needs a cached per-profile tool list to readx-mcp-headerannotations from. No server in the catalog annotates a parameter, so it is unreachable with the seeded list and reachable with a server you add.
Working on it
Requires macOS 26.0 and Xcode 26. The Swift half is xcodebuild, named by the Makefile rather than
wrapped by it.
make app # build Bastion.app (Debug)
make run # build and launch the menu bar agent
make stop # quit it
make audit # assert the listener is loopback-only and refuses foreign Origin/Host
make dialect # assert both protocol eras against a running build
make builtin # assert Bastion's own server: the write gate, and the secrets wall
make unit # assert the dialect translation and the HTTP parser
make remote-check # assert the endpoint rules, the SSE reader, and the OAuth client
make remote-live-check # assert the remote transport against two real servers: Stripe's 401, Cloudflare Docs' tools/list
make install # install the Debug build to /Applications
make bundle # build, stage node + npm, verify the install path, and sign a Release
make build-release # bundle, then notarize and staple (needs AC_* credentials)
make wiring-check # assert the config merge leaves other people's files alone
make wiring-check-real # the same, against your actual client configs (read-only)
make smoke # prove one supervised server end to endThe manifest and the JavaScript half:
make servers # regenerate every copy of the server list from servers.json
make servers-check # fail if any generated copy has drifted
make catalog-check # fail if servers.json disagrees with the servers themselves
make lint
make formatservers-check and catalog-check point in opposite directions and neither substitutes for the
other. The first asserts every generated copy matches the manifest; the second asserts the
manifest matches the servers it describes — that a declared variable is one the server actually
reads, that a write gate is read as a boolean, and that a variable typed as a switch states the
same default its own schema does. Only the second can catch a rename upstream, and only it can
catch a boolean.default of false on a setting the server defaults to true, which the
profile editor would present as the safe choice. It reads the sibling checkout named by
MCP_ROOT (default ~/Projects/mgcrea/mgcrea-ai) and skips, passing, when there is none.
A Debug build carries its own bundle identifier, io.mgcrea.bastion.debug. That is not cosmetic:
Keychain items are scoped by app identity, so a shared identifier means a debug build reads,
overwrites and deletes the credentials the real app is holding.
docs/keychain.md covers the rest: which scope holds what, why keychain prompts
happened and what actually fixed them, and what adopting the data protection keychain would cost —
including why the entitlement is worth having for iCloud sync and not worth having to stop prompts.
Seeding a Debug build from a file
The app has a profile editor; a Debug build can also import profiles from a file, which is faster
when a checkout is rebuilt often — see DevSeed.swift. Drop
this at
~/Library/Application Support/io.mgcrea.bastion.debug/import.json:
{
"token": "dev",
"profiles": [
{
"name": "prod",
"server": "shopify",
"allowWrites": false,
"values": {
"SHOPIFY_STORE_DOMAIN": "your-store.myshopify.com",
"SHOPIFY_CLIENT_ID": "…",
"SHOPIFY_CLIENT_SECRET": "…"
}
}
]
}Launching the Debug app once moves every secret into the Keychain, writes the non-secret values to
profiles.json, leaves a bearer token in dev-token, and consumes import.json so it cannot
be re-imported. Debug-only by design: a release build that imported credentials from a file anyone
could drop in its Application Support directory would be a way to add a profile to somebody else's
gateway.
In a Debug build a checkout wins over an install, so dogfooding wants
~/Library/Application Support/io.mgcrea.bastion.debug/dev.json pointing at the checkout:
{
"node": "/opt/homebrew/opt/node@24/bin/node",
"repo": "/Users/you/Projects/mgcrea/mgcrea-ai"
}Licence
Source-available for the app, so a program that holds every credential you own can be read and compiled by the people trusting it; the notarized build is sold.
Source-available. Read it, modify it, compile it, run your own build. Binary redistribution is the reserved part. | |
| MIT. The audits in particular are more useful copied than reserved. |
The signed build | EULA. $14.99 (€14.99 in the EU), every 1.x release, every Mac you own. Thirty days, full refund. |
The check is offline and cannot become anything else: scripts/audit-listener.sh asserts on every
build that the app binds loopback and nothing else, and an activation call would be the second
exception after Updates.swift. What that costs — a refunded key keeps working until the next
release — is written down in the EULA rather than left to be discovered.
docs/licensing.md has the reasoning: what is gated and what is deliberately
not, why there is a trial as well as a refund, and why hardening the check is refused by
construction.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
No tool schema history has been recorded yet.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
111Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
The Remote MCP server acts as a standardized bridge between LLM applications (like Claude, ChatGPT, and Cursor) and external services, enabling AI agents to access external tools and resources. Its primary capability is providing a centralized search tool to discover other MCP servers and their respective tools. Unlike local implementations, it runs remotely with OAuth authentication and permission controls for security.
An authenticated remote MCP server for user-owned devices and one-shot capability invocation.
Related MCP Servers
- AlicenseAqualityBmaintenanceA macOS computer-use MCP server that grants AI agents mouse, keyboard, and screen control with a robust security model including permission profiles, app deny-lists, and audit logging.22MIT
- FlicenseNot gradedqualityBmaintenanceEnterprise-grade macOS MCP server that gives AI agents eyes, hands, and a terminal, enabling screen capture, mouse/keyboard control, shell commands, and file operations.-
- AlicenseBqualityAmaintenanceEnables AI agents to directly access native macOS services, media, system health, and administration tools through a local MCP server.4039MIT
- AlicenseAqualityAmaintenanceAn MCP server that exposes macOS-native device trust signals — the facts about a Mac that cannot be gathered from a Linux container or a cloud runner. All tools are strictly read-only.22MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/mgcrea/bastion'
If you have feedback or need assistance with the MCP directory API, please join our Discord server