Remote MCP

Let Claude, ChatGPT or another MCP client find, create and edit your grains, and run commands in app grains. Sign in with OAuth and choose a whole organization or one grain.


Grain runs a remote MCP server at https://api.rungrain.com/mcp. When you connect an MCP client to it, you choose what it can reach, either every grain in one organization or a single grain, and which permissions it gets. The client can then search and read your grains, create HTML grains, save changes, see and turn off share links, and run commands in app grains.

You don't need the Grain desktop app, the grain CLI or an API key: the client signs you in through your browser.

This is a different server from the local grain mcp server described in MCP Setup. The local server works on grains in folders on your Mac. The remote server works on them directly in Grain, so it runs from anywhere, including web chat clients.

Connect Claude Code

  1. Add the server:

    claude mcp add --transport http grain-remote https://api.rungrain.com/mcp

    The name grain-remote keeps it apart from grain, the local server from MCP Setup. Connections added earlier under another name keep working.

  2. Start Claude Code, run /mcp, select grain-remote and choose Authenticate. A browser window opens.

  3. Sign in to Grain.

  4. Under Access, choose one of these:

    • All grains in an organization (the default). Pick the organization if you're in more than one.
    • One existing grain.
    • One new app workspace, which creates an empty app grain for the agent to work in.
  5. Under Permissions, untick anything you don't want to allow. See Permissions.

  6. Choose Approve. The browser returns you to Claude Code and the tools become available.

To check the connection, ask Claude to list your grains (search_grains).

Connect another MCP client

Any client that supports remote MCP servers over Streamable HTTP with OAuth can connect. Give it the server URL:

https://api.rungrain.com/mcp

The client finds everything else itself. An unauthenticated request gets a 401 pointing to https://api.rungrain.com/.well-known/oauth-protected-resource/mcp, which names Grain's authorization server and the scopes it supports. Its metadata is at https://api.rungrain.com/.well-known/oauth-authorization-server. Clients sign in with the authorization code flow and PKCE, on the same sign-in page described above. A client identifies itself in one of two ways:

  • Client ID metadata document. The client's ID is an https URL where it publishes its name and redirect URLs. Claude (Claude.ai, Claude Desktop and Claude Code), ChatGPT, VS Code and Codex do this. Grain fetches the document when you sign in, and the sign-in page shows the host it came from, for example claude.ai.
  • Dynamic client registration. The client registers itself first. Cursor and older clients do this. A registration can list https redirect URLs, http://localhost or http://127.0.0.1 on any port, or an app link such as cursor://…. Grain drops any redirect URL it can't use and keeps the rest.

Clients whose documents are published by claude.ai, claude.com, chatgpt.com or vscode.dev show no badge. Every other client is marked Unverified app. The page always shows where you'll be sent back to, and warns you when that is an app on your computer. Only approve a connection you started yourself.

The server only accepts POST requests. Clients that only speak the older HTTP+SSE transport won't work. Browser-based clients can call it directly: it answers CORS preflight requests from any origin.

For client developers

A client can add these optional parameters to its authorization request to set what the sign-in page selects first. Each is at most 200 characters. They only preselect: the person can still change the choice, and Grain checks their access when they approve.

ParameterEffect
organization_id_hintOpens on All grains in an organization with this organization selected, if the person can build in it.
workspace_id_hintOpens on One existing grain with this grain selected, if the person can build in it.
workspace_name_hintOpens on One new app workspace with this name filled in.

organization_id_hint takes precedence over the other two. A hint for an organization or grain the person can't build in, or an unknown id, is ignored without an error. A client that holds a one-grain connection can read its organization's id from workspace_info to hint it when asking for organization access.

For an organization connection, the token response includes organization_id and organization_name alongside the tokens, on the first exchange and on every refresh. organization_name is the organization's current name, and it can be missing, for example when the person is no longer a member. Treat a missing name as unknown. One-grain connections get neither field.

Permissions

Each permission is an OAuth scope. The sign-in page lists the ones the client asked for, all ticked. A client that asks for none, or only for scopes Grain doesn't know, is offered all four. You can untick any of them, but you can't add one the client didn't ask for.

ScopeOn the sign-in pageTools
grains:readFind and read grainssearch_grains, get_grain, list_shares
grains:writeCreate grains and save changes to themcreate_grain, update_grain
shares:writeTurn off share linksrevoke_share, plus list_shares to find the link
execRun commands and write files in app grainsrun_command and write_file for an organization; exec and write_file for one grain
connections:useUse your organization's app connections (Gmail, Linear, GitHub…)app_connections_list, app_connections_search_tools, app_connections_execute, app_connections_connect, for an organization only

A connection lists only the tools its permissions allow (list_shares comes with either grains:read or shares:write). A one-grain connection also always has workspace_info, and it can't create grains. Every connection has send_feedback.

Tools

Every grain in a result has a grain_id and a url, an https://rungrain.com/w/… link that opens it in Grain. Tools that act on a grain take its grain_id. A one-grain connection can only use its own grain; any other id returns grain_not_found.

Find and read

  • search_grains: finds grains whose name or description contains every word of query, most recently updated first. Without a query it lists every grain. It returns up to limit results (1 to 50, default 20), the total, and a next_cursor to pass back as cursor for the next page. Each result shows the first 200 characters of the grain's description; get_grain returns all of it.
  • get_grain: returns a grain's name, kind (html or app), link, latest revision, and up to 100 of its files. It also returns the text of one file: path, or index.html by default. A long file comes back in parts of about 24,000 characters; pass next_offset as offset to read the next part. Binary files and files over 2 MiB come back without their content. App grains keep their files on their computer, so for them files is null; read their files with run_command or exec.
  • list_shares: lists the share links that are turned on for a grain, with each link's audience.

Create and edit

  • create_grain: creates an HTML grain from a complete HTML document (html, up to 256 KiB, saved as index.html), with a name and an optional description. An optional folder such as Reports/2026 puts it in that folder, creating it if needed; otherwise it goes at the top of the organization. The grain is private to the organization until you share it. Each call creates a new grain.
  • update_grain: writes up to 20 files (1 MiB each) into an HTML grain and saves them as a new revision. Listed files are created or replaced, and other files are kept. Earlier revisions stay in the grain's history. An optional title summarizes the change in that history. With expected_revision_id, the save is refused with remote_drift if the grain has changed since that revision. Saving content the grain already has records nothing and returns changed: false. A path can't be both a file and a folder: saving index.html/app.js when index.html is a file, or the reverse, returns path_conflict.
  • revoke_share: turns off a share link by its share_id. People who open it afterwards no longer see the grain. Turning off a link that is already off succeeds and changes nothing.

The remote server can't create or change share links. Publishing a grain makes it visible outside your organization, so it stays something a person does in Grain: an agent reading untrusted content could otherwise be tricked into publishing private work.

Feedback

  • send_feedback: sends feedback about Grain to the Grain team. source is user when you asked the AI to send it, or agent when the AI files it on its own after repeated Grain failures; kind is issue, wish or praise; text is up to 4,000 characters of prose. An optional grain_id says which grain it's about. Grain removes anything that looks like a credential before storing it, and each person can send 10 an hour and 30 a day. send_feedback needs no scope: it only sends text to the Grain team and can't read or change your grains.

Use connected apps

With connections:use, an organization connection can use the app accounts (Gmail, Linear, GitHub, Slack…) that people connected in Grain Settings → App connections. Grain picks the account (yours first, else the only one; otherwise it asks), blocks writes on read-only accounts, and records every call in the organization's activity log.

  • app_connections_list: the connected accounts, whose each one is, and whether it is read-only.
  • app_connections_search_tools: find a tool for an app and its inputs.
  • app_connections_execute: run one tool with one account. account_required and needs_connection include a connect_url for the user.
  • app_connections_connect: a link for the user to connect their own account.

Connections approved before this scope existed don't have it; sign in again to add it.

Run commands in app grains

App grains have a Linux computer. HTML grains don't, and commands on them return runtime_required.

  • run_command (organization connections): runs a shell command on the computer of the app grain named by grain_id.
  • exec (one-grain connections): runs a shell command on the connection's grain.

Both run the command with bash -lc, as the node user.

InputDetails
commandRequired. Up to 64 KiB. For anything longer, write a script to a file and run it.
cwdOptional. Absolute or relative, but it must be inside /project/workspace and must already exist. Defaults to /project/workspace.
timeout_secondsOptional. 1 to 600 (10 minutes). Defaults to 120.

The result has exit_code, stdout, stderr, timed_out, truncated and duration_ms. stdout and stderr are each capped at 1 MiB, and truncated is true when output was cut. For large output, redirect it to a file and read it in parts.

A grain runs at most two commands at the same time, counting every connection to it. Long commands send progress updates every 20 seconds if the client asks for them.

write_file creates or replaces a UTF-8 text file on an app grain's computer. On an organization connection it writes to the app grain named by grain_id; on a one-grain connection, to the connection's grain. An HTML grain returns runtime_required.

InputDetails
grain_idOrganization connections only. Required. The app grain to write to, from search_grains.
pathRequired. Relative to /project/workspace. Absolute paths and .. are rejected.
contentRequired. The full file content, up to 8 MiB.
create_dirsOptional. Creates missing parent directories. Defaults to true.

It returns the file's path and size in bytes.

workspace_info (one-grain connections) takes no input and returns workspace_id, name, organization_id, organization_name and url.

Everything an app grain's computer does happens in /project/workspace, which persists between sessions: the computer pauses when idle and resumes on the next call with its files and installed packages. If the grain has an app, its source is in /project/workspace/app. A workspace created during sign-in uses the empty template, a Vite-style runtime. See Runtime for how app grains work.

Connections made before scopes

Connections approved before organization access and scopes were added keep working exactly as before: they reach their one workspace with exec, write_file and workspace_info, and nothing else. To get the other tools, clear the client's authentication for the server (in Claude Code, /mcp → grain-remote) and sign in again.

Billing

Commands run on an app grain's computer, so usage is billed to the organization that owns the grain, the same as any other runtime use. If that organization can't run grains right now, commands return billing_blocked. Reading, creating, editing and sharing HTML grains doesn't use a computer.

Security

  • A connection acts as you. You need builder access (the builder or owner role) in the organization, and Grain checks it on every request. If you lose that access, the connection stops working right away.
  • An organization connection can reach every grain in that organization, including ones created after you approved it, but nothing in your other organizations.
  • New grains are private to the organization, and a connection can't publish them. It can only see and turn off share links people created in Grain.
  • Grain keeps a record of each connection: the app's name and client ID, what it reaches, who approved it, and when it was created and last used. Tokens are stored only as hashes.
  • The client holds an access token that lasts one hour and a refresh token that lasts 30 days from its last use. Refresh tokens rotate each time they're used. You don't handle any of these yourself.

Disconnect

Open Connected apps and sign in. It lists every app you've connected, with the workspace or organization it reaches and when it was connected and last used. Choose Disconnect to end a connection. The app's next request fails with 401, and it can't refresh its tokens.

You can also remove the server from your client. In Claude Code:

claude mcp remove grain-remote

That stops your client from using the connection, but the connection stays valid until the client revokes its tokens or you disconnect it in Connected apps. A refresh token that isn't used expires 30 days after it was last used. Removing someone's builder access cuts off their connections immediately, and deleting a grain cuts off connections limited to it.

To change what a connection reaches, clear the client's authentication for grain-remote and sign in again.

Troubleshooting

HTTP errors come back on the request. Tool errors come back as a failed tool result. exec, write_file, run_command and workspace_info put the code in error_code; the other tools start the error text with it, for example [grain_not_found] ….

ErrorWhat it meansWhat to do
401 invalid_tokenThe token is missing, expired or revoked (including from Connected apps), and the client couldn't refresh it.Authenticate again from the client. In Claude Code, run /mcp and re-authenticate grain-remote.
403 access_deniedYou no longer have builder access to the organization or grain.Ask an owner of the organization for access, or connect with an organization you can build in.
429 rate_limitedMore than 300 requests in a minute on this connection.Wait a minute and retry.
grain_not_foundThe grain doesn't exist, belongs to another organization, or you can't build in it.Use search_grains to find the right grain_id.
remote_driftThe grain changed since expected_revision_id, or while saving.Read it again with get_grain, then save again.
app_grainupdate_grain was used on an app grain, whose files live on its computer.Change its files with write_file, exec or run_command.
file_not_foundget_grain was asked for a file the grain doesn't have.Pick a path from files.
share_not_foundThe grain has no share link with this share_id.Use list_shares to see the grain's links.
path_conflictA save would make one path both a file and a folder.Pick a path that doesn't sit inside an existing file, or next to a folder of the same name.
runtime_requiredThe grain is an HTML grain with no computer.Run commands on an app grain instead.
billing_blockedThe organization that owns the grain can't run grains right now.Check the organization's billing in Grain.
invalid_cwdcwd is outside /project/workspace or doesn't exist.Use a directory inside /project/workspace, and create it first if needed.
too_many_concurrent_execsTwo commands are already running on this grain.Wait for one to finish, then retry.
command_too_largeThe command is over 64 KiB.Write it to a script file and run the script.
file_too_largeA file is over the tool's limit.Split the file, or generate it with a command.
parent_not_foundcreate_dirs is false and the parent directory doesn't exist.Create the directory, or leave create_dirs unset.
workspace_unavailableGrain couldn't reach the grain's computer.Try again shortly.
grain_unavailableGrain couldn't complete the request.Try again shortly.

If create_grain saves the grain but not its HTML, the error includes the new grain_id. Save the HTML with update_grain instead of creating another grain.

If the sign-in page says Grain couldn't use this app's client metadata, Grain couldn't fetch or read the client's metadata document, or the document doesn't list the redirect URL the client used. Try again. If it keeps happening, the problem is in the client.

If the sign-in page says You need builder access to a Grain organization to connect, your account isn't a builder or owner in any organization. Ask an owner to change your role, or sign in with a different account using Switch account.