What is MCP? How an MCP server differs from an API
By Rasket TeamLast updated 17 min read
Written for AI automation builders and developers
The short answer
MCP, the Model Context Protocol, is an open standard that lets an AI assistant find and call a service's tools by itself. An MCP server usually sits in front of an API: the API is a contract a developer codes against, and the MCP server describes the same actions so a model can choose them while it works.
MCP in plain words, and who needs it
In practice, a service runs a program called an MCP server, which publishes a list of tools any assistant can read: each has a name, a plain-language description and an input schema (the arguments it takes). The assistant reads the list, picks a tool and calls it. Nobody has to write code for that pairing, as they would against the service’s API, the interface programs use to talk to it.[1]
Here is what that looks like. You ask an assistant why a customer’s password-reset email never arrived. Without MCP, you copy an email id from a dashboard, read the API docs, and run the request yourself. With your email provider’s MCP server connected, the assistant finds a tool that looks up a sent email, calls it with the id, and tells you it bounced because the address does not exist.
You need MCP if you use an assistant to do work in other services, or if you build software assistants should be able to use. You do not need it to send receipts or newsletters: ordinary code does that better. Anthropic open-sourced MCP in November 2024[2] and in December 2025 gave it to the Agentic AI Foundation, a fund under the Linux Foundation.[3] The specification is versioned by date, and the current revision is 2026-07-28.[4]
Hosts, clients and servers
- Host: the AI application you use, such as a chat app, a code editor or an agent you wrote.
- Client: a connector inside the host. Each client talks to exactly one server.
- Server: the program that offers tools and data. An email provider, a database or a code repository can each run one.
They exchange JSON-RPC 2.0 messages, a simple format for “call this method with these arguments”.[1] One server does not see another server’s traffic: the spec keeps servers isolated and leaves cross-server interactions to the host.[21] The model sees all of it, though, and can pass what one server returned into a call to another. That is how data leaks between connected services, and why it matters which servers you connect side by side.
Tools, resources and prompts
| Primitive | Who controls it | Example |
|---|---|---|
| Tools | The model decides when to call one | Look up a bounce, send an email |
| Resources | The application attaches them as context | A file, a template, a record |
| Prompts | The user picks one, like a slash command | “Draft a reply to this” |
The spec defines that split.[5] Tools are what most servers are built for. A server lists them in answer to tools/list, each with the three parts above: the description is written for the model, and the input schema is JSON Schema.[6]
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"tools": [
{
"name": "get_email",
"description": "Look up one sent email by id: who it went to and what happened to it last.",
"inputSchema": {
"type": "object",
"properties": { "email_id": { "type": "string" } },
"required": ["email_id"]
}
}
],
"ttlMs": 300000,
"cacheScope": "private"
}
}The last two fields are caching hints, new in this revision. ttlMs is how many milliseconds the host may treat the list as fresh: 300000 is five minutes, and 0 means fetch it again each time. cacheScope is either "public", for a result with nothing user-specific that a shared gateway may cache, or "private", for one that may be reused only with the same credentials. A tool list filtered by the caller’s permissions is private.[10]
// The host calls the tool by name:
{ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": { "name": "get_email", "arguments": { "email_id": "em_123" } } }
// The server answers with structured data, and the same data as text for older clients:
{ "jsonrpc": "2.0", "id": 2, "result": {
"resultType": "complete",
"content": [{ "type": "text", "text": "{\"id\":\"em_123\",\"last_event\":\"bounced\"}" }],
"structuredContent": { "id": "em_123", "last_event": "bounced" },
"isError": false } }A tool that declares an outputSchema must return structuredContent that matches it, so the host can check the data before the model reads it. A failure the model could fix, such as a bad date, comes back as a result with isError: true, not as a protocol error, so the model can try again.[6]
When a server needs more from the user, it now answers with resultType: "input_required" and its questions, and the client repeats the call with the answers.[15] Roots, sampling and logging are deprecated: they keep working for at least twelve months, but new servers should not adopt them. Pass folders as tool arguments, call a model provider directly, and log to stderr or OpenTelemetry instead.[11]
How MCP messages travel: stdio and Streamable HTTP
- stdio: the host starts the server as a program on your own machine and talks to it through its standard input and output. Local tools work this way.
- Streamable HTTP: each message is an HTTP POST to one endpoint on a remote server. Hosted services work this way.
The older HTTP+SSE transport has been deprecated since the 2025-03-26 revision.[8]
MCP is stateless since 2026-07-28
Guides written before this year describe an initialize handshake and a session id header. The 2026-07-28 revision removed both. Each change has its own proposal (SEP), linked here:
| Change | What your server does now | Proposal |
|---|---|---|
| No sessions | Keep state behind a handle one tool returns and the next takes as an argument | SEP-2567[12] |
| No initialize handshake | Read the protocol version and client capabilities from every request, and answer server/discover | SEP-2575[13] |
| Caching hints | Put ttlMs and cacheScope on list results | SEP-2549[14] |
| resultType | Mark every result complete or input_required | SEP-2322[15] |
| Roots, sampling, logging deprecated | Stop relying on them in new code | SEP-2577[16] |
Each request carries its version in the io.modelcontextprotocol/protocolVersion key of its _meta, and over HTTP also in an MCP-Protocol-Version header that must match, beside Mcp-Method and, for a tool call, Mcp-Name headers that repeat the method and the tool’s name.[4] A client can ask first with server/discover:
// Request
{ "jsonrpc": "2.0", "id": 1, "method": "server/discover",
"params": { "_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {} } } }
// Response from the example server below
{ "jsonrpc": "2.0", "id": 1, "result": {
"supportedVersions": ["2026-07-28"],
"capabilities": { "tools": { "listChanged": true } },
"resultType": "complete", "ttlMs": 0, "cacheScope": "private",
"_meta": { "io.modelcontextprotocol/serverInfo": { "name": "shop", "version": "1.0.0" } } } }The example server lists only 2026-07-28 there although it serves both eras: older clients never call server/discover, they open with initialize, and the SDK answers that the old way. A server that does not support the version a request names answers HTTP 400 with an error listing the versions it does, and the client retries with one of them. A server from before 2026-07-28 cannot send that error, so when a 400 carries no modern error, a client that also speaks the older revisions falls back to initialize. One that speaks only 2026-07-28 cannot connect.[8] The full list of changes is in the changelog.[7] Rasket’s own server is still on the handshake revisions: its section says which.
How an MCP server decides who is calling
Authorization is optional in MCP. A stdio server takes its credentials from the environment of the program that started it.[9] So a local server runs with whatever keys and tokens sit in your shell: run only local servers you trust.
A remote server that requires sign-in follows a profile of OAuth 2.1, the standard behind most “Sign in with …” screens. For you, it looks like a browser window asking you to approve access. Underneath:[9]
- The server publishes where to get a token (OAuth Protected Resource Metadata, RFC 9728).
- The client asks for a token for that one server (resource indicators, RFC 8707), so a token for one server cannot be replayed against another.
- Clients should identify themselves with a Client ID Metadata Document. Dynamic Client Registration still works but is deprecated.[7]
- A server must not accept, or pass on, tokens issued for something else.
The tool list may depend on that token. The spec’s own example is returning “only the tools the caller’s granted scopes permit”.[6] A server built that way hides a tool from a client with no permission for it, so the model never sees it.
When no one is at the keyboard
The standard flow needs a person to approve once. After that, an agent can keep working for as long as the server’s refresh tokens last, if its authorization server issues them at all. For a job that never has a person, an optional extension, io.modelcontextprotocol/oauth-client-credentials, lets a service sign in with its own credentials. Extensions are opt-in, and support varies by client and server.[17] If the server you need does not offer it, call that service’s REST API with a key instead.
MCP server vs API: what actually differs
Your own code keeps calling the API; the MCP server is a second door onto the same service, shaped for a model. The spec does not compare the two, so this table is our reading of it.
| REST API | MCP server | |
|---|---|---|
| Who reads the contract | A developer, from documentation, before writing code | The model, from tool descriptions it fetches at run time |
| Who decides to call | Your code, at a line you wrote | The model; the spec says a person should be able to deny the call |
| Discovery | Read the docs; each client is written for one API | tools/list; one host works with any server |
| Shape | Resources and HTTP verbs: POST /emails | Named actions with a schema: send_email |
| Credentials | Usually an API key in your environment | Usually an OAuth token you approved, often with narrower scopes |
| State | Stateless requests | Stateless requests since 2026-07-28; handles for anything longer |
The difference that matters most is who holds the decision. With an API you choose every call when you write the code. With MCP the model chooses at run time, from descriptions, and a wrong description, a broad grant or a manipulated input changes what it does. The spec says there should always be a human in the loop able to deny a tool call.[6] Hosts differ in how often they ask, so check yours.
When to use an MCP server, and when an API is enough
- Use the API when the same thing happens every time: send the receipt when an order is paid, sync contacts each night. Code is cheaper, testable and predictable.
- Use an MCP server when a person works through an assistant and the next step depends on what they ask: set up a domain, find out why a message bounced, draft a reply.
- Build your own MCP server when you want any assistant to reach your product. If only your own app calls a model, plain function calling inside that app is simpler.
Group tools by job, and let a client load one group. Every tool’s name, description and schema sit in the model’s context, so a server with dozens of tools should not hand all of them to a client that needs three.
Build a minimal MCP server in front of your REST API
The smallest useful server wraps one endpoint you already have. This one uses @modelcontextprotocol/server 2.3.1, version 2 of the official TypeScript SDK, which implements 2026-07-28 and still serves clients that open with the old handshake.[18] The shop API is invented: point SHOP_API_URL at yours. The tool lives in one file, and two short files serve it locally or over the network.
# Node 20 or later
npm install @modelcontextprotocol/server@2.3.1 @modelcontextprotocol/node@2.1.1 zod@4// shop-tools.mjs: one MCP tool in front of one REST endpoint.
import { McpServer } from "@modelcontextprotocol/server";
import * as z from "zod/v4";
const API = process.env.SHOP_API_URL; // your existing REST API
const KEY = process.env.SHOP_API_KEY; // read from the environment, never from the model
if (!API || !KEY) throw new Error("Set SHOP_API_URL and SHOP_API_KEY");
/** What the tool promises to return, and nothing more. */
const Order = z.object({ id: z.string(), status: z.string(), total: z.string(), email: z.string() });
/** A failed call is still a tool result. Its text tells the model whether retrying can help. */
const failure = (text) => ({ content: [{ type: "text", text }], isError: true });
export function makeServer() {
const server = new McpServer({ name: "shop", version: "1.0.0" });
server.registerTool(
"get_order",
{
description:
"Look up one order by its id (ord_…). Returns its status, total and the email it was sent to. " +
"Use it when the user asks where an order is or why a confirmation did not arrive.",
inputSchema: z.object({ order_id: z.string().describe("The order id, such as ord_123") }),
outputSchema: Order,
annotations: { readOnlyHint: true, openWorldHint: false },
},
async ({ order_id }) => {
let res;
try {
res = await fetch(`${API}/orders/${encodeURIComponent(order_id)}`, {
headers: { authorization: `Bearer ${KEY}` },
signal: AbortSignal.timeout(10_000),
});
} catch {
return failure("The shop API did not answer. Try once more in a minute.");
}
if (res.status === 404) return failure(`No order ${order_id}. Check the id with the user.`);
if (res.status === 429 || res.status >= 500) return failure("The shop API is busy. Try once more in a minute.");
if (!res.ok) return failure(`The shop API refused the call (HTTP ${res.status}). Do not retry; tell the user.`);
const parsed = Order.safeParse(await res.json().catch(() => null));
if (!parsed.success) return failure("The shop API answered in an unexpected shape. Tell the user.");
const order = parsed.data; // only the declared fields, so the result matches outputSchema
return { content: [{ type: "text", text: JSON.stringify(order) }], structuredContent: order };
},
);
return server;
}Every failure is a tool result with isError: true: the spec puts API failures there, next to bad input.[6] What changes is the text. A 404 tells the model to check the id with the user; no answer, a 429 or a 5xx tells it to try once more later; any other refusal tells it not to retry. The API’s raw error body stays on your side. A found order is parsed against the same schema it declares as outputSchema, so only the declared fields go back, once as structuredContent and once as text for older clients.
Run it on your machine
// server.mjs: the host starts this on your machine.
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import { makeServer } from "./shop-tools.mjs";
serveStdio(makeServer);The host starts it and passes your key in its environment. Many desktop hosts read a block of this shape from their configuration file; check yours for the file’s name:
{
"mcpServers": {
"shop": {
"command": "node",
"args": ["/path/to/server.mjs"],
"env": { "SHOP_API_URL": "https://shop.example.com/api", "SHOP_API_KEY": "sk_test_123" }
}
}
}To call the tool without an assistant, use the MCP Inspector’s command-line mode:
npx @modelcontextprotocol/inspector@2.11.0 --cli \
-e SHOP_API_URL=https://shop.example.com/api -e SHOP_API_KEY=sk_test_123 \
node server.mjs --method tools/call --tool-name get_order --tool-arg order_id=ord_123{
"content": [
{
"type": "text",
"text": "{\"id\":\"ord_123\",\"status\":\"shipped\",\"total\":\"42.00\",\"email\":\"ana@example.com\"}"
}
],
"structuredContent": {
"id": "ord_123",
"status": "shipped",
"total": "42.00",
"email": "ana@example.com"
}
}Put it on the network
// server-http.mjs: the same tool as a remote server behind OAuth.
import http from "node:http";
import { toNodeHandler } from "@modelcontextprotocol/node";
import { createMcpHandler } from "@modelcontextprotocol/server";
import { makeServer } from "./shop-tools.mjs";
const ORIGIN = process.env.PUBLIC_URL; // where clients reach this server, e.g. https://mcp.shop.example.com
const AUTH_SERVER = process.env.AUTH_SERVER; // who issues its tokens
if (!ORIGIN || !AUTH_SERVER) throw new Error("Set PUBLIC_URL and AUTH_SERVER");
const METADATA = "/.well-known/oauth-protected-resource";
const mcp = toNodeHandler(createMcpHandler(makeServer));
// Stand-in: ask your authorization server whether the token is live and was issued for ORIGIN/mcp.
const tokenIsValid = async (token) => Boolean(token) && token === process.env.DEMO_TOKEN;
http
.createServer(async (req, res) => {
if (req.headers.origin && req.headers.origin !== new URL(ORIGIN).origin) {
res.writeHead(403); // the spec's guard against DNS rebinding
return res.end();
}
const path = new URL(req.url, ORIGIN).pathname;
if (path === METADATA) {
res.setHeader("content-type", "application/json");
return res.end(JSON.stringify({ resource: `${ORIGIN}/mcp`, authorization_servers: [AUTH_SERVER] }));
}
const token = req.headers.authorization?.replace(/^Bearer /, "");
if (path !== "/mcp" || !(await tokenIsValid(token))) {
res.writeHead(401, { "www-authenticate": `Bearer resource_metadata="${ORIGIN}${METADATA}"` });
return res.end();
}
return mcp(req, res);
})
.listen(Number(process.env.PORT ?? 3000), "127.0.0.1"); // behind your HTTPS proxyA remote server has to say where tokens come from. It publishes the RFC 9728 metadata document naming its authorization server, and answers a request with no valid token with 401 and a WWW-Authenticate header pointing at that document, which is how a client knows where to send you to sign in.[9] tokenIsValid is a stand-in: replace it with your authorization server’s check of signature, expiry and audience. The 403 on a foreign Origin is a rule of the transport, against DNS rebinding.[8] It listens on 127.0.0.1 because it belongs behind your HTTPS reverse proxy, which ends TLS and forwards to it.
We ran all three files on 17 October 2026 against a stand-in for the shop API. Over stdio, server/discover answered as shown above, a 2025-11-25 initialize was answered too, and an unknown order, a 503, an unreachable API and an answer with missing fields each came back as isError: true with the text above. An extra field from the API was dropped. Over HTTP, the metadata document, the 401 (with no token configured at all, too), the 403 and both eras’ calls behaved as described.
Write descriptions the model can act on
- Say when to use the tool, not only what it does. The example names the two questions it answers.
- Describe every argument and its format in the schema, with an example value. The model sees nothing else.
- Use names of 1 to 128 letters, digits,
_,-and., and return tools in the same order every time so hosts can cache the list.[6] - Set
readOnlyHintanddestructiveHinttruthfully. A host can use them to decide when to ask the user first.
What an MCP server adds to your threat model
- Consent. The spec’s principles say a host must get your consent before invoking a tool, and its tools section says a person should be able to deny any call.[1][6] As a server builder, make the risky step a second call: return a preview first, and act only when the client calls again with a token from that preview.
- Untrusted labels. Clients “MUST consider tool annotations to be untrusted unless they come from trusted servers”.[6] A tool from an unknown server that says it is read-only may not be.
- Data between servers. The model can move one server’s output into another’s tool call. The spec asks clients to show tool inputs to you before a call, to catch exactly that.[6]
- Instructions hidden in data. Anything the model reads, such as an email or a web page, can carry text written to steer it. This is prompt injection. Label fetched content as data in the tool result, keep send tools out of a connection that reads untrusted mail when you can, and require a person’s yes before anything leaves.
- Real-world side effects. The spec says servers must rate limit tool calls.[6] For a send tool, that limit is what stops a confused model from mailing a whole list.
Rasket’s MCP server
Rasket runs a remote MCP server at https://api.rasket.com/mcp over Streamable HTTP. It accepts OAuth only, so an assistant never holds your API key, and the scopes you approve decide which tools exist for that connection. Every call shows in your Logs, and you can disconnect an app under Settings, Team, Authorized apps.
It is built on version 1 of the official TypeScript SDK, which accepts revisions 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05 through the initialize handshake, and 2024-10-07, a draft from before the first published revision.[19] It does not speak 2026-07-28 yet: that SDK answers a request naming any other version in its MCP-Protocol-Version header with HTTP 400.[20] Clients that also speak the older revisions connect: we checked with version 2.3.1 of the official TypeScript client, which negotiated 2025-11-25 with a server built the way ours is.
| Group | Tools | Scopes |
|---|---|---|
| setup | 26 | team:read, team:write, domains:read, domains:write, webhooks:read, webhooks:write, templates:read, templates:write |
| send | 11 | emails:send, emails:read, suppressions:read, suppressions:write, logs:read |
| audience | 20 | contacts:read, contacts:write, segments:read, segments:write, topics:read, topics:write |
| marketing | 17 | broadcasts:read, broadcasts:write, automations:read, automations:write, events:read, events:write, events:send |
| ai | 3 | ai:use |
| billing | 1 | billing:read |
| inbox | 9 | inbox:read, inbox:write |
describe_setup comes with every group. A sign-in asks for the inbox group’s scopes only when you name it, and ?toolset=send,audience loads two groups. The MCP reference lists every tool with its group and scope, and the same four limits:
| Limit | Allowed |
|---|---|
| Tool calls | 60 a minute |
| Single sends (send_email) | 100 a rolling hour |
| Campaigns (send_broadcast) | 3 a rolling hour |
| Contact imports | 5 a rolling hour |
// The arguments an assistant passes to Rasket's send_email
{ "from": "Shop <orders@shop.example.com>", "to": ["ana@example.com"],
"subject": "Your order shipped", "text": "Order ord_123 is on its way.",
"idempotency_key": "ord_123-shipped" }
// The structuredContent of the result
{ "id": "01a148a9-b240-74cb-a37e-6a4bf97fd0d4", "replayed": false }A campaign and a support Inbox reply each take two calls: the first returns a preview and a confirm_token, and only a second call with that token sends. The token proves the model saw the preview, not that a person agreed, since a model could make the second call itself. What puts a person in the loop is your host asking before a destructive tool runs, so set send tools to ask. A single send_email has no second step, which is what its hourly limit is for.
MCP answers only when the assistant asks: nothing in it tells an agent that mail arrived or bounced. To react to those, point a webhook at your own code and start the agent from there. Receiving email with a webhook covers inbound mail.
There is no client-credentials sign-in. A person approves the app once. Its access token lasts 1 hour, the client renews it with a refresh token that lasts 30 days, and after 90 days someone approves it again. For a job with no person at all, use the REST API with a sending_access key, which can send and do nothing else.
# Claude Code
claude mcp add --transport http rasket https://api.rasket.com/mcp
# Or only the send and audience groups (Claude Code accepts a narrowed address; a claude.ai connector does not yet)
claude mcp add --transport http rasket "https://api.rasket.com/mcp?toolset=send,audience"
# Claude Code's .mcp.json (other clients name the keys differently)
{ "mcpServers": { "rasket": { "type": "http", "url": "https://api.rasket.com/mcp" } } }Letting an AI agent send email with MCP walks through the setup, and the MCP reference lists every tool with its scope. If you only need to send from your own code, use the email API instead.
Frequently asked questions
Is MCP only for Claude?
No. MCP is an open standard, and any AI application can act as a host: chat apps, code editors and agent frameworks from different companies connect to the same servers. That is the point of it, since one server then works with every host that speaks the protocol.
Is MCP the same as function calling?
No. Function calling is how a model asks your code to run a function you described in one API request. MCP standardises where those descriptions come from: a server publishes its tools once, and any host can offer them to its model.
Do I need an SDK to build an MCP server?
No. MCP is JSON-RPC 2.0 over stdio or HTTP, so any language can speak it. An SDK saves you the version negotiation, schema conversion and transport details, and can serve clients of both protocol eras from one tool definition.
What do MCP tool annotations default to?
A tool with no annotations is treated as possibly destructive and open-world: destructiveHint and openWorldHint default to true, readOnlyHint and idempotentHint to false. Set all four, because hosts use them to decide when to ask you first.
Should an MCP tool return JSON or text?
Both, when the data has structure. Put the object in structuredContent, ideally declared by an outputSchema the host can check, and the same data serialised as text in content, which the spec asks for so older clients still see it.
How do I keep state between MCP tool calls without sessions?
Return a handle from the tool that creates the state, such as a basket_id, and take it as an argument on the calls that follow. Check the caller's authorization against the handle on every call, and say in the tool's description when it expires.
Which protocol revision should a new MCP server support?
The current one, 2026-07-28, plus the 2025-11-25 handshake for as long as the hosts your users run still open with initialize. Version 2 of the TypeScript SDK serves both from one tool definition, so supporting both costs no extra code.
Sources
21 primary sources, each read on the date shown.
See all sourcesHide sources
- 1.Specification (revision 2026-07-28) — Model Context Protocol,
- 2.Introducing the Model Context Protocol — Anthropic,
- 3.Donating the Model Context Protocol and establishing the Agentic AI Foundation — Anthropic,
- 4.Versioning — Model Context Protocol,
- 5.Server features: overview — Model Context Protocol,
- 6.Server features: tools — Model Context Protocol,
- 7.Key changes in revision 2026-07-28 — Model Context Protocol,
- 8.Transports — Model Context Protocol,
- 9.Authorization — Model Context Protocol,
- 10.Server utilities: caching — Model Context Protocol,
- 11.Deprecated features (revision 2026-07-28) — Model Context Protocol,
- 12.SEP-2567: remove protocol-level sessions — Model Context Protocol on GitHub,
- 13.SEP-2575: stateless requests and server/discover — Model Context Protocol on GitHub,
- 14.SEP-2549: caching hints on list results — Model Context Protocol on GitHub,
- 15.SEP-2322: multi round-trip requests and resultType — Model Context Protocol on GitHub,
- 16.SEP-2577: deprecate roots, sampling and logging — Model Context Protocol on GitHub,
- 17.OAuth Client Credentials extension — Model Context Protocol,
- 18.TypeScript SDK v2 documentation — Model Context Protocol,
- 19.TypeScript SDK 1.30.0: supported protocol versions (src/types.ts) — Model Context Protocol on GitHub,
- 20.TypeScript SDK 1.30.0: Streamable HTTP server transport — Model Context Protocol on GitHub,
- 21.Architecture — Model Context Protocol,
Keep reading
- DocsMCP serverConnect Claude, ChatGPT or any MCP client: your scopes, no key.
- FeatureFor AI agentsAn MCP server, a setup skill, Markdown at every docs URL and an OpenAPI document, so an assistant can set Rasket up and send for you with no key in the prompt.
- FeatureEmail APISend email over one REST call: idempotent sends, batches, scheduling, attachments, templates and a delivery timeline for every message.
- GlossaryAPI keyAn API key is the credential a program uses to call an email API. It identifies the account, carries a set of permissions, and travels in an authorization header rather than in the URL. A key is a secret: keep it in your server's environment, never in client code or a repository, and revoke it the moment you suspect it has leaked.
Written by the Rasket Team. First published ; last checked against its sources . See something out of date? How we correct guides.