@paymanai/genie-mcp-stdio
v0.4.0
Published
Genie for any stdio-only MCP host: a local stdio bridge to the remote Genie MCP server
Maintainers
Readme
genie-mcp-stdio
Genie for any MCP host that runs local stdio servers.
Using an AI coding agent? Don't read this — hand it over. Paste this into Claude Code, Codex, Cursor, or whatever you use:
Integrate Genie (Payman's payments agent) into my MCP host by following the "For agents" section of https://raw.githubusercontent.com/PaymanAI/genie-mcp-stdio/main/README.md exactly. Detect which host I use, add the server, run the sign-in, and verify.Your agent will add one server entry, open your browser once for you to sign in to Genie, and confirm the connection. Nothing to copy, no keys to obtain.
For agents
You are integrating Genie into the user's MCP host. Follow these steps in order. Do not improvise credentials: there are no API keys, tokens or client ids to obtain or ask the user for. The sign-in is a browser flow the bridge runs itself.
1. Check prerequisites
node --version # must print v20 or newerIf Node is missing or older than 20, install Node 20+ (or tell the user to) before
continuing. Nothing else is needed; npx fetches the bridge on first run.
2. Detect the host and add the server
Look for the host's config in this order; use the first that exists on the machine (or
the one the user named). Add the genie entry, merging into any existing mcpServers
object rather than replacing the file. Add no env block.
| Host | Config file | Entry |
|---|---|---|
| OpenMausBot | ~/.openmausbot/config.json | JSON below, under mcpServers |
| Claude Desktop (macOS) | ~/Library/Application Support/Claude/claude_desktop_config.json | JSON below, under mcpServers |
| Claude Desktop (Windows) | %APPDATA%\Claude\claude_desktop_config.json | JSON below, under mcpServers |
| Claude Code | run claude mcp add genie -- npx -y @paymanai/genie-mcp-stdio | — |
| Cursor | ~/.cursor/mcp.json (global) or <project>/.cursor/mcp.json | JSON below, under mcpServers |
| Windsurf | ~/.codeium/windsurf/mcp_config.json | JSON below, under mcpServers |
| Codex CLI | ~/.codex/config.toml | TOML below |
| Anything else that launches stdio MCP servers | its MCP config | command npx, args -y @paymanai/genie-mcp-stdio, no env |
JSON entry:
{
"mcpServers": {
"genie": {
"command": "npx",
"args": ["-y", "@paymanai/genie-mcp-stdio"]
}
}
}TOML entry (Codex CLI):
[mcp_servers.genie]
command = "npx"
args = ["-y", "@paymanai/genie-mcp-stdio"]Claude Code, Claude Desktop and Codex CLI can also add https://genie.paymanai.com/mcp
directly as a remote server with OAuth. That only works once Payman has registered the
host's OAuth client, because Genie has no dynamic client registration; see
Integrating Genie without the bridge.
Until then, or when in doubt, use the bridge above; it works everywhere.
3. Sign the user in
Run this in a terminal the user can see, and tell them a browser window will open:
npx -y @paymanai/genie-mcp-stdio loginIt prints Signed in to Genie. and Sign-in stored in …/genie-mcp-stdio/credentials.json
on success. If no browser opens, give the user the URL printed after
Sign in to Genie in your browser:. It waits up to fifteen minutes. You can skip this step —
the bridge signs in on the first tool call instead — but running it now means the user's
first message doesn't stall on a browser.
4. Verify
Restart the host (or reload its MCP servers). Confirm a server named genie exposing the
tool ask_genie (and the four *_genie_agent / *_genie_access self-service tools) appears. Its single argument is request — the person's ask, in plain
words ({"request": "what can you help me with?"}). Have the user (or the host's agent)
send Genie a harmless request like that and check that a reply comes back. On first use Genie
asks the user to connect a finance provider — that is expected and happens in Genie's own
UI, not in the host. If the request fails, see Troubleshooting.
5. Report back
Tell the user, in one or two lines: which config file you changed, that they are signed
in to Genie, and that npx -y @paymanai/genie-mcp-stdio logout signs them out everywhere.
Do not
- Do not set
GENIE_ACCESS_TOKEN,GENIE_INTEGRATION_KEY,GENIE_OAUTH_CLIENT_IDor any otherGENIE_*variable. They exist for Payman's own deployments and integrators; a person's setup uses none of them, and asking the user for such values is a red flag. - Do not copy
credentials.jsoninto the host's config or anywhere else. - Do not ask the user for their Genie password; the bridge never sees it either.
What this is
Genie is Payman's payments agent. It is reachable as a
remote MCP server (https://genie.paymanai.com/mcp, Streamable HTTP) exposing one tool,
ask_genie. Some hosts, like OpenMausBot,
only launch local stdio MCP servers. Others, like Claude Code, Claude Desktop and Codex
CLI, can connect to remote MCP URLs, but Genie only accepts OAuth clients that Payman has
registered. This package works for both today: a local process that speaks stdio to the host
and Streamable HTTP to Genie, forwarding tools verbatim and relaying Genie's
elicitation prompts back to the host when the host supports them.
It decides nothing. You sign in to your own Genie account once in the browser; the bridge keeps that sign-in in a private file and never puts a secret in a host's configuration, tool output or logs.
Commands
| Command | What it does |
|---|---|
| npx -y @paymanai/genie-mcp-stdio | Serve Genie over stdio. This is what hosts run. |
| npx -y @paymanai/genie-mcp-stdio login | Sign in now: opens the browser, waits up to fifteen minutes for the callback, stores the sign-in. |
| npx -y @paymanai/genie-mcp-stdio logout | Revoke the sign-in at Genie and delete the local copy. |
All diagnostics go to stderr, prefixed [genie-mcp-stdio]; stdout is reserved for MCP.
How the sign-in works
The bridge is a preregistered native OAuth client at Genie (genie-mcp-stdio):
authorization code with S256 PKCE, a loopback redirect on a random port (RFC 8252), and a
refresh token that Genie rotates on every use and lets live for 90 days from the last
use. Access tokens last one hour and are renewed silently; you only see the browser again
if you have not used Genie for 90 days, sign out, or Genie revokes the sign-in.
The browser has fifteen minutes to finish. If it takes longer, the bridge stops waiting
but keeps its loopback port open, so a late browser sees "This sign-in expired" with what
to do next rather than a refused connection; and if the port is gone (a login command
that already exited), Genie's own page notices you came back and explains the same thing.
Either way nothing was connected, and asking the agent again starts a fresh sign-in.
The refresh token is stored in ~/.config/genie-mcp-stdio/credentials.json
($XDG_CONFIG_HOME respected), mode 0600, keyed by Genie URL. It is never written to a
host's configuration, tool output or logs. logout revokes it at Genie (RFC 7009) before
deleting it, so a copied file stops working too.
Configuration (you almost certainly don't need this)
A person's setup uses no environment variables at all. These exist for Payman's own local Genie stacks, CI, and organizations integrating on their own behalf rather than as a person:
| Variable | Meaning |
|---|---|
| GENIE_MCP_URL | Defaults to https://genie.paymanai.com/mcp. Must be https; plain http is accepted only for loopback (local Genie stacks). |
| GENIE_OAUTH_CLIENT_ID | Leave unset. Defaults to genie-mcp-stdio, the bridge's own preregistered public client id (not a secret). Only a self-hosted Genie, or a fork we registered separately, sets this. |
| GENIE_BROWSER_COMMAND | Command (space-separated) that receives the sign-in URL; defaults to the OS opener. |
| GENIE_CREDENTIALS_FILE | Where the sign-in is stored; defaults as above. |
| GENIE_ACCESS_TOKEN | Bypass the account sign-in with a Genie OAuth access token you obtained elsewhere (expires; no refresh). |
| GENIE_INTEGRATION_KEY + GENIE_CUSTOMER_ID (+ GENIE_CUSTOMER_EMAIL) | Bypass the account sign-in with a managed integration key; this identifies an organization's integration acting for a customer, not a person's own account. |
Setting both bypass variables is refused, because Genie refuses a request that carries both.
What the host sees
- Tools: whatever Genie lists — today
ask_genie, which takes one argument,request: what the person wants, in plain words, plus four self-service tools the assistant uses to manage its own Genie record (get_genie_agent,rename_genie_agent,grant_genie_read_access,request_genie_access) — read-only access it can grant itself; full access it asks for and the person approves on Genie's Agents page. Sign-ins made by bridge 0.3.x carry onlygenie:ask; runnpx -y @paymanai/genie-mcp-stdio logout, then sign in again, to pick upgenie:self.tools/list_changedis forwarded. - Elicitation: Genie asks the person to connect their account and pick a finance
provider on first use. If the host declared the
elicitationcapability the prompt is relayed to it; otherwise Genie's reply explains what to do instead. - Errors: a rejected credential surfaces as an MCP error naming what to check, never a token value. The bridge starts even when Genie is unreachable, so a problem shows up on the first call rather than as a silent missing server. If Genie stops accepting the stored sign-in, the bridge opens the browser again rather than failing.
Troubleshooting
OpenMausBot on macOS: every bot reply fails with "MCP server could not start"
The full message is MCP server could not start; check its command and installation, and it
appears even though the server's Test button in OpenMausBot lists ask_genie.
OpenMausBot's chat starts MCP servers without the user's shell PATH, so it cannot find
npx; its Test button adds that PATH, which is why the test passes.
The fix is an env that holds only PATH on the genie entry. This is the one exception to
"Add no env block" above. Print the value in the user's shell:
printf '%s:/usr/bin:/bin:/usr/sbin:/sbin\n' "$(dirname "$(command -v npx)")"Add it to the entry in ~/.openmausbot/config.json:
"env": { "PATH": "<the line printed above>" }Then have the user quit OpenMausBot (Cmd+Q) and open it again, without changing MCP servers in the app first: a hand edit needs the restart, and an in-app MCP change before then writes the old entry back. People doing this by hand can follow docs/hosts/openmausbot.md.
Security notes
- One session per bridge process, bound by Genie to the authenticated caller. Restarting the host restarts the session.
- Genie never returns a bank credential to a model; the bridge never sees one either. This package only moves JSON-RPC.
- The bridge collapses concurrent token refreshes into one request: the MCP session's
POST and its notification stream can both meet a
401at the same moment, and Genie treats a second use of a rotated refresh token as a replay that revokes the sign-in. logoutwhen you stop using a machine; the sign-in is then dead at Genie, not just deleted locally.
Integrating Genie without the bridge
If you maintain a host and want to connect to Genie's remote MCP server directly — the better long-term answer, and the one OpenMausBot has on its roadmap — here is what Genie expects. It is standard MCP authorization; nothing here is Genie-specific except the client registration.
- Transport. Streamable HTTP at
https://genie.paymanai.com/mcp. POSTs answer with JSON; notifications and elicitation requests arrive on the standaloneGETSSE stream, so open it afterinitialize. Sessions are bound to the authenticated caller viaMcp-Session-Id; a new process needs a new session. - Discovery. An unauthenticated request returns
401withWWW-Authenticate: Bearer resource_metadata="https://genie.paymanai.com/.well-known/oauth-protected-resource/mcp", scope="genie:ask genie:self"(RFC 9728). That document names the authorization server, whose RFC 8414 metadata lists the authorize, token, revocation and JWKS endpoints. - Authorization. Authorization code with S256 PKCE,
token_endpoint_auth_method: none, scopegenie:ask genie:self(genie:askalone still works, without the self-service tools), and theresourceparameter set to the MCP URL (RFC 8707). There is no dynamic client registration: ask us to preregister your host with itsclient_id, display name and exact redirect URIs (open an issue on this repository). Registered loopback redirects for native apps match on any port; everything else matches exactly. Native clients receive a refresh token (90 days sliding, rotated on every use — send the new one back next time, and never reuse an old one, which Genie treats as a replay and revokes the sign-in). Access tokens are one-hour ES256 JWTs. - Elicitation. Declare the
elicitationcapability if you can render a form: Genie uses it once per account to have the person connect a finance provider, and carries aconnectionUrlin_metayou may open for them. Without it,ask_geniereplies with instructions instead. - Test against the real thing. This bridge's
test/fixture.tsandtest/fakeAuthServer.tsare a faithful local stand-in for the above if you want an offline test; the production server behaves the same way.
Development
nvm use # Node 24
npm install
npm test # builds, then node:test against an in-process fake Genietest/fixture.ts is a small Streamable HTTP MCP server with Genie's admission rule
(exactly one credential or 401 with a Bearer challenge) and an ask_genie that can
elicit on the standalone stream, the way Genie does. test/fakeAuthServer.ts is the slice
of Genie's authorization server the sign-in uses — discovery, PKCE, refresh rotation,
revocation — and test/browser.ts stands in for the person's browser.
License
MIT.
