branch:
client.tsx
6792 bytesRaw
import { createRoot } from "react-dom/client";
import { ThemeProvider } from "@cloudflare/agents-ui/hooks";
import { ModeToggle, PoweredByAgents } from "@cloudflare/agents-ui";
import { Badge, Surface, Text } from "@cloudflare/kumo";
import {
  ShieldCheckIcon,
  WrenchIcon,
  GlobeIcon,
  TerminalIcon,
  InfoIcon
} from "@phosphor-icons/react";
import "./styles.css";

const TOOLS = [
  {
    name: "hello",
    description:
      "Returns a greeting — uses the authenticated username if no name is provided"
  },
  {
    name: "whoami",
    description:
      "Returns the authenticated user's profile (userId, username, email)"
  }
];

const ENDPOINTS = [
  {
    path: "/mcp",
    description: "MCP server endpoint (requires Bearer token)"
  },
  {
    path: "/.well-known/oauth-authorization-server",
    description: "OAuth server metadata (discovery)"
  },
  {
    path: "/oauth/register",
    description: "Dynamic client registration"
  },
  { path: "/authorize", description: "OAuth authorization" },
  { path: "/oauth/token", description: "Token exchange" }
];

function App() {
  return (
    <div className="h-full flex flex-col bg-kumo-base">
      <header className="px-5 py-4 border-b border-kumo-line">
        <div className="max-w-3xl mx-auto flex items-center justify-between">
          <div className="flex items-center gap-3">
            <ShieldCheckIcon
              size={22}
              className="text-kumo-accent"
              weight="bold"
            />
            <h1 className="text-lg font-semibold text-kumo-default">
              Authenticated MCP Server
            </h1>
            <Badge variant="secondary">v1.0.0</Badge>
          </div>
          <ModeToggle />
        </div>
      </header>

      <main className="flex-1 overflow-auto p-5">
        <div className="max-w-3xl mx-auto space-y-8">
          <Surface className="p-4 rounded-xl ring ring-kumo-line">
            <div className="flex gap-3">
              <InfoIcon
                size={20}
                weight="bold"
                className="text-kumo-accent shrink-0 mt-0.5"
              />
              <div>
                <Text size="sm" bold>
                  Authenticated MCP Server
                </Text>
                <span className="mt-1 block">
                  <Text size="xs" variant="secondary">
                    This MCP server is protected by OAuth 2.1 using{" "}
                    <code className="text-xs px-1 py-0.5 rounded bg-kumo-elevated font-mono">
                      @cloudflare/workers-oauth-provider
                    </code>
                    . Clients register dynamically, complete the authorization
                    flow, and use a Bearer token to call tools. Inside tool
                    handlers, the auth context is available via{" "}
                    <code className="text-xs px-1 py-0.5 rounded bg-kumo-elevated font-mono">
                      getMcpAuthContext()
                    </code>
                    . Use the MCP Inspector to test the full OAuth flow.
                  </Text>
                </span>
              </div>
            </div>
          </Surface>

          <section>
            <div className="flex items-center gap-2 mb-3">
              <WrenchIcon
                size={18}
                weight="bold"
                className="text-kumo-subtle"
              />
              <Text size="base" bold>
                Tools
              </Text>
              <Badge variant="secondary">{TOOLS.length}</Badge>
            </div>
            <div className="space-y-2">
              {TOOLS.map((tool) => (
                <Surface
                  key={tool.name}
                  className="p-4 rounded-xl ring ring-kumo-line"
                >
                  <Text size="sm" bold>
                    {tool.name}
                  </Text>
                  <span className="mt-0.5 block">
                    <Text size="xs" variant="secondary">
                      {tool.description}
                    </Text>
                  </span>
                </Surface>
              ))}
            </div>
          </section>

          <section>
            <div className="flex items-center gap-2 mb-3">
              <GlobeIcon size={18} weight="bold" className="text-kumo-subtle" />
              <Text size="base" bold>
                Endpoints
              </Text>
            </div>
            <div className="space-y-2">
              {ENDPOINTS.map((ep) => (
                <Surface
                  key={ep.path}
                  className="p-3 rounded-xl ring ring-kumo-line flex items-center justify-between"
                >
                  <code className="text-sm font-mono text-kumo-default">
                    {ep.path}
                  </code>
                  <Text size="xs" variant="secondary">
                    {ep.description}
                  </Text>
                </Surface>
              ))}
            </div>
          </section>

          <section>
            <div className="flex items-center gap-2 mb-3">
              <TerminalIcon
                size={18}
                weight="bold"
                className="text-kumo-subtle"
              />
              <Text size="base" bold>
                Testing
              </Text>
            </div>
            <Surface className="p-4 rounded-xl ring ring-kumo-line space-y-3">
              <Text size="sm" variant="secondary">
                Connect using the{" "}
                <a
                  href="https://github.com/modelcontextprotocol/inspector"
                  className="underline text-kumo-accent"
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  MCP Inspector
                </a>
                :
              </Text>
              <pre className="text-sm font-mono bg-kumo-elevated p-3 rounded-lg overflow-x-auto text-kumo-default">
                npx @modelcontextprotocol/inspector
              </pre>
              <Text size="xs" variant="secondary">
                Set the transport to <strong>Streamable HTTP</strong> and URL to{" "}
                <code className="text-xs px-1 py-0.5 rounded bg-kumo-elevated font-mono">
                  http://localhost:5173/mcp
                </code>
                . The inspector will handle the OAuth flow automatically.
              </Text>
            </Surface>
          </section>
        </div>
      </main>

      <footer className="border-t border-kumo-line py-3">
        <div className="flex justify-center">
          <PoweredByAgents />
        </div>
      </footer>
    </div>
  );
}

createRoot(document.getElementById("root")!).render(
  <ThemeProvider>
    <App />
  </ThemeProvider>
);