v0.3.1
MCP servers are managed from the command line and logged in to. iota mcp add|list|get|remove edits the mcp_servers: block of one file; a server behind
OAuth 2.1 is one iota mcp login <name> signs in to — the client identified
three ways, the consent prompt asked for, auth discovered from the server's
own 401 — the shape one Logto tenant taught, a finding at a time.
Added
iota mcpmanages themcp_servers:block.iota mcp add <name> -- <command> [args…]oradd <name> --url <url> [--header 'K: V']…,list [--scope user|project|all] [--json] [--probe],get <name>andremove <name> [--scope]. Two scopes and no third file:--scope user(~/.iota.yaml, the default) or--scope project(./.iota.yaml, where a header or environment value must be a${…}reference), or the-cfile alone. The block is machine-managed — every other byte of the file stays as you wrote it; a comment inside the block is not kept.- MCP OAuth 2.1. A server that asks for a login is one you log in to:
iota mcp login <name>discovers the authorization server, registers a client when the server offers it, opens the browser ($BROWSER, else the platform opener; the URL is printed either way, and a pasted redirect URL works without a browser) and stores the tokens in~/.iota/mcp/auth/<name>.json(mode 0600). A run puts the bearer token on every request and refreshes it when the server rejects it; a server with no usable token is reported asnot logged in: run iota mcp login <name>and left out of that run alone, and a stored login the server will not refresh is reported with the server's answer and the sameloginto run.iota mcp logout <name>revokes and forgets. In the chat,/mcpshows every server's login state,/mcp login <name>runs the same flow and reconnects the server,/mcp logout <name>takes it down. - A login identifies iota three ways. The entry's
client_id(a client registered out of band —iota mcp add … --client-id <id> --client-secret-env VAR, the secret only ever a${env:VAR}reference; or--client-idonlogin), else dynamic registration when the server offers it, else iota's Client ID Metadata Document (https://iota.sh/oauth/client.json) when the server accepts one — the shape of an authorization server like Logto, which registers nobody. A login asks for the scopes the resource names, plusoffline_accesswhen the server lists it, and carries the RFC 8707resource. - A pre-registered client listens on a fixed port. Its redirect URI must match
what was registered, so
loginbinds127.0.0.1:17801(or the entry'sredirect_port,--redirect-portonadd) for it and reports a taken port instead of moving;iota mcp get/listshow theredirect_urito register, andloginprints it asRedirect:. A refusal names the server'serror_descriptionanderror_uri. - The authorization request asks for the consent prompt. Every
loginsendsprompt=consent(OpenID Connect Core §3.1.2.1), as Claude Code does; a server that does not know the parameter ignores it (RFC 6749 §3.1). A Logto tenant answered a request without it with a bareaccess_deniedright after the consent page. authis discovered. An HTTP server no longer has to be declaredauth: oauthto be logged in to. Not written (the default) isauto: a run connects bare, and a 401 at the handshake is the server asking for a login — that server is reported asnot logged in: run iota mcp login <name>and left out of the run, not "connect failed"; once a token file exists for its name, every later connect is an OAuth one.auth: oauthforces the login,auth: noneforbids it, and an entry that writes its ownAuthorizationheader counts asnone— that credential is the one to fix, as Claude Code has it.iota mcp add --urlwrites noauth:unless--auth oauth|nonesays so;list/getshowauto(auto: logged inwith a token file), and--probelabels a server that askedneeds login: iota mcp login <name>.