← Vytvářejte agenty na Cloudflare / agents / model-context-protocol / guides
Zpracování OAuth se servery MCP
Při připojování k serverům MCP chráněným pomocí OAuth (například Slack nebo Notion) se musí vaši uživatelé ověřit, než bude moci váš Agent přistupovat k jejich datům. Tato příručka popisuje, jak implementovat OAuth toky, aby autorizace proběhla pro uživatele co nejjednodušeji.
Jak to funguje
- Zavolejte
addMcpServer()s URL serveru - Pokud je vyžadováno OAuth,
authUrlse vrátí namísto okamžitého připojení - Zobrazte
authUrlvašemu uživateli (přesměrování, vyskakovací okno nebo odkaz) - Uživatel se ověří na webu poskytovatele
- Poskytovatel přesměruje zpět na callback URL vašeho Agenta
- Váš agent automaticky dokončí připojení
Klient MCP používá vestavěný DurableObjectOAuthClientProvider k bezpečné správě stavu OAuth: ukládá nonce a ID serveru, ověřuje je při zpětném volání a po použití nebo vypršení platnosti je odstraní.
Zahájení OAuth
Při připojování k serveru chráněnému pomocí OAuth zkontrolujte, zda authUrl se vrátí. Pokud je přítomen, přesměrujte uživatele k dokončení autorizace:
export class MyAgent extends Agent {
async onRequest(request) {
const url = new URL(request.url);
if (url.pathname.endsWith("/connect") && request.method === "POST") {
const { id, authUrl } = await this.addMcpServer(
"Cloudflare Observability",
"https://observability.mcp.cloudflare.com/mcp",
);
if (authUrl) {
// OAuth required - redirect user to authorize
return Response.redirect(authUrl, 302);
}
// Already authenticated - connection complete
return Response.json({ serverId: id, status: "connected" });
}
return new Response("Not found", { status: 404 });
}
}export class MyAgent extends Agent<Env> {
async onRequest(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname.endsWith("/connect") && request.method === "POST") {
const { id, authUrl } = await this.addMcpServer(
"Cloudflare Observability",
"https://observability.mcp.cloudflare.com/mcp",
);
if (authUrl) {
// OAuth required - redirect user to authorize
return Response.redirect(authUrl, 302);
}
// Already authenticated - connection complete
return Response.json({ serverId: id, status: "connected" });
}
return new Response("Not found", { status: 404 });
}
}Alternativní přístupy
Místo automatického přesměrování můžete zobrazit authUrl vašemu uživateli jako:
- Vyskakovací okno:
window.open(authUrl, '_blank', 'width=600,height=700')pro aplikace ve stylu dashboardu - Klikatelný odkaz: Zobrazte jako tlačítko nebo odkaz pro vícekrokové postupy
- Deep link: Použijte vlastní schémata URL pro mobilní aplikace
Konfigurace chování callbacku
Po dokončení OAuth vás poskytovatel přesměruje zpět na callback URL vašeho Agenta. Ve výchozím nastavení úspěšné ověření přesměruje na origin vaší aplikace, zatímco neúspěšné ověření zobrazí chybovou stránku HTML s textem chyby.
Přesměrování do vaší aplikace
Po dokončení OAuth přesměrujte uživatele zpět do své aplikace:
export class MyAgent extends Agent {
onStart() {
this.mcp.configureOAuthCallback({
successRedirect: "/dashboard",
errorRedirect: "/auth-error",
});
}
}export class MyAgent extends Agent<Env> {
onStart() {
this.mcp.configureOAuthCallback({
successRedirect: "/dashboard",
errorRedirect: "/auth-error",
});
}
}Uživatelé se vracejí na /dashboard při úspěchu nebo /auth-error?error=<message> při selhání.
Zavřít vyskakovací okno
Pokud jste OAuth otevřeli ve vyskakovacím okně, po dokončení ho automaticky zavřete:
import { Agent } from "agents";
export class MyAgent extends Agent {
onStart() {
this.mcp.configureOAuthCallback({
customHandler: () => {
// Close the popup after OAuth completes
return new Response("<script>window.close();</script>", {
headers: { "content-type": "text/html" },
});
},
});
}
}import { Agent } from "agents";
export class MyAgent extends Agent<Env> {
onStart() {
this.mcp.configureOAuthCallback({
customHandler: () => {
// Close the popup after OAuth completes
return new Response("<script>window.close();</script>", {
headers: { "content-type": "text/html" },
});
},
});
}
}Vaše hlavní aplikace může detekovat zavření vyskakovacího okna a obnovit stav připojení. Pokud OAuth selže, stav připojení se změní na "failed" a chybová zpráva se uloží do server.error pro zobrazení ve vašem UI.
Sledování stavu připojení
Aplikace React
Použijte useAgent hook pro aktualizace v reálném čase přes WebSocket:
import { useAgent } from "agents/react";
import { useState } from "react";
function App() {
const [mcpState, setMcpState] = useState({
prompts: [],
resources: [],
servers: {},
tools: [],
});
const agent = useAgent({
agent: "my-agent",
name: "session-id",
onMcpUpdate: (mcpServers) => {
// Automatically called when MCP state changes!
setMcpState(mcpServers);
},
});
return (
<div>
{Object.entries(mcpState.servers).map(([id, server]) => (
<div key={id}>
<strong>{server.name}</strong>: {server.state}
{server.state === "authenticating" && server.auth_url && (
<button onClick={() => window.open(server.auth_url, "_blank")}>
Authorize
</button>
)}
{server.state === "failed" && server.error && (
<p className="error">{server.error}</p>
)}
</div>
))}
</div>
);
}import { useAgent } from "agents/react";
import { useState } from "react";
import type { MCPServersState } from "agents";
function App() {
const [mcpState, setMcpState] = useState<MCPServersState>({
prompts: [],
resources: [],
servers: {},
tools: [],
});
const agent = useAgent({
agent: "my-agent",
name: "session-id",
onMcpUpdate: (mcpServers: MCPServersState) => {
// Automatically called when MCP state changes!
setMcpState(mcpServers);
},
});
return (
<div>
{Object.entries(mcpState.servers).map(([id, server]) => (
<div key={id}>
<strong>{server.name}</strong>: {server.state}
{server.state === "authenticating" && server.auth_url && (
<button onClick={() => window.open(server.auth_url, "_blank")}>
Authorize
</button>
)}
{server.state === "failed" && server.error && (
<p className="error">{server.error}</p>
)}
</div>
))}
</div>
);
} onMcpUpdate callback se spustí automaticky při změně stavu MCP, není potřeba žádné dotazování (polling).
Ostatní frameworky
Stav připojení zjišťujte dotazováním na endpoint:
export class MyAgent extends Agent {
async onRequest(request) {
const url = new URL(request.url);
if (
url.pathname.endsWith("connection-status") &&
request.method === "GET"
) {
const mcpState = this.getMcpServers();
const connections = Object.entries(mcpState.servers).map(
([id, server]) => ({
serverId: id,
name: server.name,
state: server.state,
isReady: server.state === "ready",
needsAuth: server.state === "authenticating",
authUrl: server.auth_url,
}),
);
return Response.json(connections);
}
return new Response("Not found", { status: 404 });
}
}export class MyAgent extends Agent<Env> {
async onRequest(request: Request): Promise<Response> {
const url = new URL(request.url);
if (
url.pathname.endsWith("connection-status") &&
request.method === "GET"
) {
const mcpState = this.getMcpServers();
const connections = Object.entries(mcpState.servers).map(
([id, server]) => ({
serverId: id,
name: server.name,
state: server.state,
isReady: server.state === "ready",
needsAuth: server.state === "authenticating",
authUrl: server.auth_url,
}),
);
return Response.json(connections);
}
return new Response("Not found", { status: 404 });
}
}Průběh stavů připojení: authenticating (vyžaduje OAuth) → connecting (dokončení nastavení) → ready (k dispozici pro použití)
Zpracování selhání
Když OAuth selže, stav připojení se změní na "failed" a chybová zpráva se uloží do server.error pole. Zobrazte tuto chybu ve svém uživatelském rozhraní a umožněte uživatelům opakovat pokus:
import { useAgent } from "agents/react";
import { useState } from "react";
function App() {
const [mcpState, setMcpState] = useState({
prompts: [],
resources: [],
servers: {},
tools: [],
});
const agent = useAgent({
agent: "my-agent",
name: "session-id",
onMcpUpdate: setMcpState,
});
const handleRetry = async (serverId, serverUrl, name) => {
// Remove failed connection
await fetch(`/agents/my-agent/session-id/disconnect`, {
method: "POST",
body: JSON.stringify({ serverId }),
});
// Retry connection
const response = await fetch(`/agents/my-agent/session-id/connect`, {
method: "POST",
body: JSON.stringify({ serverUrl, name }),
});
const { authUrl } = await response.json();
if (authUrl) window.open(authUrl, "_blank");
};
return (
<div>
{Object.entries(mcpState.servers).map(([id, server]) => (
<div key={id}>
<strong>{server.name}</strong>: {server.state}
{server.state === "failed" && (
<div>
{server.error && <p className="error">{server.error}</p>}
<button
onClick={() => handleRetry(id, server.server_url, server.name)}
>
Retry Connection
</button>
</div>
)}
</div>
))}
</div>
);
}import { useAgent } from "agents/react";
import { useState } from "react";
import type { MCPServersState } from "agents";
function App() {
const [mcpState, setMcpState] = useState<MCPServersState>({
prompts: [],
resources: [],
servers: {},
tools: [],
});
const agent = useAgent({
agent: "my-agent",
name: "session-id",
onMcpUpdate: setMcpState,
});
const handleRetry = async (
serverId: string,
serverUrl: string,
name: string,
) => {
// Remove failed connection
await fetch(`/agents/my-agent/session-id/disconnect`, {
method: "POST",
body: JSON.stringify({ serverId }),
});
// Retry connection
const response = await fetch(`/agents/my-agent/session-id/connect`, {
method: "POST",
body: JSON.stringify({ serverUrl, name }),
});
const { authUrl } = await response.json();
if (authUrl) window.open(authUrl, "_blank");
};
return (
<div>
{Object.entries(mcpState.servers).map(([id, server]) => (
<div key={id}>
<strong>{server.name}</strong>: {server.state}
{server.state === "failed" && (
<div>
{server.error && <p className="error">{server.error}</p>}
<button
onClick={() => handleRetry(id, server.server_url, server.name)}
>
Retry Connection
</button>
</div>
)}
</div>
))}
</div>
);
}Běžné důvody selhání:
- Uživatel zrušil: Okno OAuth bylo zavřeno před dokončením autorizace
- Neplatné přihlašovací údaje: Přihlašovací údaje poskytovatele byly nesprávné
- Přístup odepřen: Uživateli chybí požadovaná oprávnění
- Vypršená relace: Vypršel časový limit relace OAuth
Neúspěšná připojení zůstávají ve stavu, dokud nejsou odebrána pomocí removeMcpServer(serverId). Chybová zpráva se automaticky escapuje, aby se zabránilo útokům XSS, takže ji lze bezpečně zobrazit přímo v uživatelském rozhraní.
Kompletní příklad
Tento příklad ukazuje kompletní integraci OAuth s Cloudflare Observability. Uživatelé se připojí, autorizují se ve vyskakovacím okně a připojení se poté zpřístupní. Chyby se automaticky ukládají do stavu připojení, aby se daly zobrazit ve vašem UI.
import { Agent, routeAgentRequest } from "agents";
export class MyAgent extends Agent {
onStart() {
this.mcp.configureOAuthCallback({
customHandler: () => {
// Close popup after OAuth completes (success or failure)
return new Response("<script>window.close();</script>", {
headers: { "content-type": "text/html" },
});
},
});
}
async onRequest(request) {
const url = new URL(request.url);
// Connect to MCP server
if (url.pathname.endsWith("/connect") && request.method === "POST") {
const { id, authUrl } = await this.addMcpServer(
"Cloudflare Observability",
"https://observability.mcp.cloudflare.com/mcp",
);
if (authUrl) {
return Response.json({
serverId: id,
authUrl: authUrl,
message: "Please authorize access",
});
}
return Response.json({ serverId: id, status: "connected" });
}
// Check connection status
if (url.pathname.endsWith("/status") && request.method === "GET") {
const mcpState = this.getMcpServers();
const connections = Object.entries(mcpState.servers).map(
([id, server]) => ({
serverId: id,
name: server.name,
state: server.state,
authUrl: server.auth_url,
}),
);
return Response.json(connections);
}
// Disconnect
if (url.pathname.endsWith("/disconnect") && request.method === "POST") {
const { serverId } = await request.json();
await this.removeMcpServer(serverId);
return Response.json({ message: "Disconnected" });
}
return new Response("Not found", { status: 404 });
}
}
export default {
async fetch(request, env) {
return (
(await routeAgentRequest(request, env, { cors: true })) ||
new Response("Not found", { status: 404 })
);
},
};import { Agent, routeAgentRequest } from "agents";
type Env = {
MyAgent: DurableObjectNamespace<MyAgent>;
};
export class MyAgent extends Agent<Env> {
onStart() {
this.mcp.configureOAuthCallback({
customHandler: () => {
// Close popup after OAuth completes (success or failure)
return new Response("<script>window.close();</script>", {
headers: { "content-type": "text/html" },
});
},
});
}
async onRequest(request: Request): Promise<Response> {
const url = new URL(request.url);
// Connect to MCP server
if (url.pathname.endsWith("/connect") && request.method === "POST") {
const { id, authUrl } = await this.addMcpServer(
"Cloudflare Observability",
"https://observability.mcp.cloudflare.com/mcp",
);
if (authUrl) {
return Response.json({
serverId: id,
authUrl: authUrl,
message: "Please authorize access",
});
}
return Response.json({ serverId: id, status: "connected" });
}
// Check connection status
if (url.pathname.endsWith("/status") && request.method === "GET") {
const mcpState = this.getMcpServers();
const connections = Object.entries(mcpState.servers).map(
([id, server]) => ({
serverId: id,
name: server.name,
state: server.state,
authUrl: server.auth_url,
}),
);
return Response.json(connections);
}
// Disconnect
if (url.pathname.endsWith("/disconnect") && request.method === "POST") {
const { serverId } = (await request.json()) as { serverId: string };
await this.removeMcpServer(serverId);
return Response.json({ message: "Disconnected" });
}
return new Response("Not found", { status: 404 });
}
}
export default {
async fetch(request: Request, env: Env) {
return (
(await routeAgentRequest(request, env, { cors: true })) ||
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;