MCP
Any Model Context Protocol server’s tools can plug into an agent. The client speaks Streamable HTTP with zero new dependencies. Auth is a token string, or a lambda that re-resolves once on a 401 so refresh logic lives in one place.
linear = Mistri::MCP::Client.new( url: "https://mcp.linear.app/mcp", token: -> { connection.bearer_token },)
tools = Mistri::MCP.tools(linear, prefix: "linear", gates: { "create_issue" => true })
agent = Mistri.agent("claude-opus-4-8", tools: tools)prefix: namespaces the tools, allow: / deny: filter them, and
gates: puts a human approval in front of the risky ones, exactly like a
native tool.
Local servers and browsers
Local stdio servers spawn as child processes with credentials in their environment. That is the whole “give the agent a browser” story:
browser = Mistri::MCP::Client.new( command: ["npx", "-y", "@playwright/mcp@latest", "--browser", "chrome", "--headless"],)browser_tools = Mistri::MCP.tools( browser, allow: %w[browser_navigate browser_snapshot],)agent = Mistri.agent("claude-opus-4-8", tools: browser_tools)OAuth, as your application
For user-connected servers, generate a connection model in Rails and name it whatever you like:
$ bin/rails generate mistri:mcp McpConnectionEach row is one server connection carrying its own OAuth state and encrypted tokens:
connection, authorize_url = McpConnection.connect( name: "Linear", url: params[:url], client_name: "YourApp", redirect_uri: mcp_callback_url,)# redirect the user; then, in the callback:connection = McpConnection.complete(state: params[:state], code: params[:code])
agent = Mistri.agent("claude-opus-4-8", tools: connection.tools(prefix: "linear"))The services underneath (Mistri::MCP::OAuth.start, .complete,
.refresh) are storage-agnostic, so the same flow works from a
controller, a GraphQL mutation, or a job. Registration happens as your
application, never as the harness: client_name: is yours.