← All posts

Connect any AI tool to your workspace over MCP

A practical guide to the Model Context Protocol - what a server and a client are, a remote URL with OAuth versus a local stdio process, how consent and scopes should work, why a client should confirm before it writes, and how Rezee does each of these.

Oct 20, 2026 · 12 min read · Kash Gohil · LinkedIn · X

Most AI tools can now work with your real data instead of a pasted copy of it. The agent in your terminal can read an issue. The assistant in your editor can open a pull request. A chat in your browser can summarize a channel. The standard that makes this possible is the Model Context Protocol, or MCP. This guide explains how an MCP connection works and what a safe one looks like. It also gives a checklist to use before you connect a tool to anything you care about. The last part shows how Rezee does it, but the first part applies to any MCP server.

What is MCP, in plain words?

MCP has two sides. A server owns some data and offers a list of tools: named actions with a typed list of arguments, like issue_get or pull_merge. A client is the AI application. It reads the list, gives it to the model, and runs the tools the model picks.

The conversation between them is small. It is JSON-RPC, and three methods do most of the work:

  1. initialize. The client and the server agree on a protocol version. The server can also send instructions for the model.
  2. tools/list. The server returns each tool with its name, a description and a JSON Schema for its arguments.
  3. tools/call. The client asks the server to run one tool with arguments, and the server returns the result as text or an image.

A call looks like this:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "issue_get",
    "arguments": { "workspace": "rezee", "number": 42 }
  }
}

The model never sees your password or your database. It sees tool names and results. The client decides which calls go out, and the server decides which calls it accepts. Most of this guide is about those two decisions.

Clients come in four kinds, and each kind connects a little differently:

  • A terminal agent runs in your shell and edits files in your repository.
  • A code editor runs an assistant beside your open files.
  • A web chat runs in a browser tab on the vendor's servers.
  • A desktop app runs on your machine with its own window.

A web chat cannot start a process on your laptop. A terminal agent can. That one fact decides which transport you can use.

Remote URL or local process?

An MCP server reaches a client in one of two ways.

A remote URL. The server runs on the internet at an HTTPS address. The client sends each JSON-RPC message as an HTTP POST. You give the client the URL, and the client signs you in through your browser with OAuth. Every kind of client can use this, and it is the only option for a web chat.

A local process over stdio. The client starts a program on your machine. It talks to that program through standard input and output, one JSON message per line. The program holds your credential and forwards the calls. This works for terminal agents, editors and desktop apps. It does not work for a web chat, because the chat has no access to your machine.

Remote URL Local stdio
Setup Paste a URL, sign in Install a program, add a command
Who holds the credential The client, as an OAuth token The local program
Works in a web chat Yes No
Grant is visible in your account Yes, as a connected app Depends on the server
Updates The server changes, nothing to install You update the program

Neither is better in every case. We like the remote URL as the default, because the grant is an object you can see and revoke. The local process is useful when you already use the vendor's CLI and are signed in, or when the client does not support OAuth yet.

One thing to avoid with both: a long-lived token pasted into a JSON config file. That file gets copied into dotfile repositories and backups. The token in it usually has every permission you have, and nothing tells you when it is used.

How should sign-in work on a remote server?

The MCP specification uses OAuth 2.1 for remote servers, and the flow is now mostly automatic. A client that follows the specification needs only the server URL. This is the sequence:

  1. The client calls the server with no token. The server answers 401 with a WWW-Authenticate header. The header names a metadata document.
  2. The client reads that document (RFC 9728). It names the authorization server.
  3. The client reads the authorization server's own metadata (RFC 8414). That gives the sign-in, token and registration endpoints.
  4. The client identifies itself. It can register (Dynamic Client Registration), or it can use an HTTPS URL as its client id that points to a metadata document it hosts.
  5. Your browser opens a consent screen. You sign in and choose what the client may do.
  6. The client exchanges a one-time code for an access token. PKCE proves that the client that started the flow is the client that finishes it.
  7. The client sends the token on every call.

You see only step 5. Two details in the other steps are worth checking on any server. The first is PKCE with S256: a client on a laptop cannot keep a secret, and PKCE is what stands in for one. The second is that the token should be bound to one resource (RFC 8707), so a token for one server is useless at another.

The consent screen is the part you control, so it is where a good server and a careless one differ most. A careless one asks "Allow this app to access your account?" with one button. A good one makes the grant specific. These are the rules we think every server should follow:

  • Show each permission on its own row. "Read issues" and "Write pull requests" are different decisions.
  • Tick reads and leave writes unticked. A connector that can read nothing cannot do anything useful. But a write should be a choice you make, not a default you accept.
  • Never offer some permissions at all. A browser consent screen is the wrong place to grant push access or CI secrets. A good server refuses those to connectors, whatever the client requests.
  • Say who the actions are attributed to. If the tool acts as you, the screen should say so, and your audit log should show it.
  • Keep every grant visible and revocable. One list of connected apps, with a Disconnect button on each row.

When a client needs more later, it should ask for more. The server answers a call outside the grant with 403 and an insufficient_scope challenge that names the missing scope. The client then sends you back to the consent screen for that one permission. This is called step-up. It is better than a wide grant at the start, and better than a failure the model cannot explain.

Why should a client confirm before it writes?

Scopes decide what a client is able to do. They do not decide what it should do without asking. You might grant write access to pull requests so that an agent can open one. That same grant also lets it merge one. The fix belongs in the client: it should stop and ask before some calls.

MCP gives the client the information it needs. Each tool in tools/list can carry annotations:

  • readOnlyHint: the tool changes nothing.
  • destructiveHint: the tool can delete something or do something that you cannot undo.
  • idempotentHint: two identical calls have the same effect as one call.
  • openWorldHint: the tool reaches systems outside the server.

A sensible client policy uses them like this. Reads run without a prompt. Ordinary writes ask once, or ask until you say "always allow this tool". Destructive tools ask every time and show the arguments. Many terminal agents and editors already work this way. If yours has an auto-approve setting, read what it does before you turn it on.

Annotations are hints, not enforcement. The specification tells clients to treat them as untrusted unless they come from a server you trust. A malicious server can mark a delete as read-only. So the annotations are only as good as the server that sends them. Look for a server that derives them from what each tool really does.

A checklist before you connect a server

Before you connect a tool to any workspace, check these:

  1. Can you connect with a URL and a browser sign-in, so you do not paste a token?
  2. Does the consent screen list permissions one by one, with writes unticked?
  3. Are some permissions never offered to connectors?
  4. Do access tokens expire in hours, not months?
  5. Can you see and revoke the connection without a support ticket?
  6. Do the tools carry readOnlyHint and destructiveHint?
  7. Does your client ask before destructive calls?

How Rezee does it

Rezee is an MCP server for one workspace that holds issues, code, pull requests, CI/CD, docs, chat and design. The MCP page has setup steps for each client, and the MCP reference has the protocol details. This section shows how each point above works in Rezee. An earlier post explains why the tools and the web app call the same API, so we do not repeat that here.

Two ways to connect. The server URL is https://api.rezee.app/api/mcp. Any client that supports OAuth needs only that URL. For a client that wants a local process, install the CLI:

curl -fsSL https://rezee.app/install.sh | sh

Then give your client the command rezee mcp. It speaks the stdio transport and forwards each message to the same server. First it looks for a token in the environment, then for one from an earlier rezee auth login. If it finds neither, it opens a browser to sign you in. It writes only protocol messages to stdout, and everything else goes to stderr. Use --no-browser over SSH to print the sign-in URL instead.

Discovery and registration. A call with no token gets 401 and a challenge that points to /.well-known/oauth-protected-resource/api/mcp. Both metadata documents are public, because a client must read them before it has a credential. Clients can register through Dynamic Client Registration or use a client id metadata URL. PKCE with S256 is required, and plain is not offered. Redirects must match exactly, and plain http is allowed only for loopback addresses, so a desktop client can listen on 127.0.0.1.

Consent. The consent screen says "This app will act as you, and everything it does is recorded under your name." Reads arrive ticked. That covers code, issues, pull requests, docs, chat and CI runs. It also covers your account, your workspace list, agent tasks and agents. Writes are offered unticked. These include issues:write, pulls:write, docs:write, chat:write, branches:write and runs:write. The server intersects what you ticked with what the client requested, so the screen can only narrow a grant.

Five capabilities are never offered to a connector, and a client cannot step up into them: repos:write, code:write, secrets:write, releases:write and tasks:run. The result is that a connector cannot push with git. It reads code and opens pull requests through the API. It also cannot create repositories, touch CI secrets, cut releases or drive another runtime's agent tasks.

Tokens. An access token lasts one hour and is bound to the MCP server. A refresh token lasts 30 days. Each refresh rotates the token: the old refresh token is revoked, and a new pair is issued. If a spent refresh token comes back, Rezee treats it as theft and revokes every token in that grant. A replayed authorization code does the same. A code is valid for one minute and one use. Your account settings list each connected app once, with a Disconnect button that revokes its tokens.

Annotations. Rezee has over 290 tools, and every one carries annotations. They are computed from the request each tool makes, not written by hand. A GET is readOnlyHint. A DELETE is destructiveHint. So are the 16 tools whose effects you cannot take back, such as pull_merge, doc_publish and canvas_share. A test checks the catalogue against the real requests, so the two cannot drift apart.

A worked example

Here is a short session from a terminal agent in our demo workspace. You connect with the default grant and ask: "What is left on REZ-42?"

The server's instructions tell the model that every tool needs a workspace slug and that workspaces_list returns it. So the agent calls workspaces_list and gets rezee. Then it calls issue_get and issue_code. All three are reads, so the client runs them without a prompt. The agent replies that REZ-42 is "Encrypt pipeline secrets at rest", that kobeni merged web#128, and that web#131 is still open.

Then you say: "Merge web#131." The agent calls pull_merge. The default grant has pulls:read but not pulls:write. The server answers 403 with error="insufficient_scope", scope="pulls:write". A client that supports step-up opens the consent screen for that one permission. You tick it. The client retries, and this time it sees destructiveHint: true on pull_merge and asks you to confirm, with the repository and the number shown. You say yes, and the merge runs as you.

Two separate checks stopped that merge. The server checked your grant, and the client checked with you. If either had been missing, the other would still have caught it.

Connectors are not agents

Rezee also has agents: workspace members with their own name and token. An agent like kobeni must stop for a person before any of the 16 irreversible tools, even when its token allows them. That gate checks the caller. If the caller is not an agent, the gate does not apply.

So the gate does not apply to your connector. A connector acts as you, and you can merge your own pull requests. This is a deliberate choice. Your protection on a connector is the grant you made and the client's confirmation. The gate is for work you delegated and are not watching. Branch protection and repository permissions still apply to both.

FAQ

Do I need Rezee to use anything in this guide?

No. The first half describes MCP itself and what any server should do. Use the checklist on any server you connect.

Should I use the remote URL or rezee mcp?

Use the URL if your client supports OAuth. The grant then appears in your account settings, and you can revoke it there. Use rezee mcp if your client only runs local commands, or if you already use the CLI.

Can a client I connected push code?

No. code:write is never offered to a connector. If you grant branches:write, a connector can create a branch and write a file to it through the API. If you grant pulls:write, it can open a pull request. It cannot push with git.

What if my client does not support OAuth?

Send a personal access token as Authorization: Bearer reze_pat_.... This works, but it is the pasted-token pattern this guide warns about. Give the token only the scopes the client needs.

Does one connection reach all my workspaces?

Yes. Every tool takes a workspace slug, and the connection acts as you. It can reach each workspace you are a member of, with the permissions you have there.