Adds an example MCP App server demonstrating lazy (on-demand) OAuth: the server connects and lists tools without authentication, and only prompts for auth when a protected tool is called, by responding 401 with a WWW-Authenticate header pointing at protected-resource metadata. - Public `show_auth_button` app calls the protected `get_secret` tool via callServerTool; the host runs the OAuth flow on 401 and retries - Embedded mock authorization server (authorization-code + PKCE, short-lived HS256 tokens, refresh + session revocation) so the whole flow runs from a single process - TTL-scoped endpoint paths (/ttl/<seconds>/mcp) let slow or automated clients request longer-lived tokens per connection, threaded through OAuth via RFC 8707 resource indicators - `elicit_url` / `elicit_by_error` tools demonstrating URL elicitation
57 lines
1.6 KiB
TypeScript
57 lines
1.6 KiB
TypeScript
/**
|
|
* @file Protected app view: renders the result of the `get_secret` tool.
|
|
*
|
|
* This view is only reached after the host has completed the OAuth flow
|
|
* (the tool call that produces it requires a Bearer token).
|
|
*/
|
|
import {
|
|
App,
|
|
applyDocumentTheme,
|
|
applyHostFonts,
|
|
applyHostStyleVariables,
|
|
type McpUiHostContext,
|
|
} from "@modelcontextprotocol/ext-apps";
|
|
import "./global.css";
|
|
import "./mcp-app.css";
|
|
|
|
const mainEl = document.querySelector(".main") as HTMLElement;
|
|
const outputEl = document.getElementById("output")!;
|
|
|
|
function handleHostContextChanged(ctx: McpUiHostContext) {
|
|
if (ctx.theme) {
|
|
applyDocumentTheme(ctx.theme);
|
|
}
|
|
if (ctx.styles?.variables) {
|
|
applyHostStyleVariables(ctx.styles.variables);
|
|
}
|
|
if (ctx.styles?.css?.fonts) {
|
|
applyHostFonts(ctx.styles.css.fonts);
|
|
}
|
|
if (ctx.safeAreaInsets) {
|
|
mainEl.style.paddingTop = `${ctx.safeAreaInsets.top}px`;
|
|
mainEl.style.paddingRight = `${ctx.safeAreaInsets.right}px`;
|
|
mainEl.style.paddingBottom = `${ctx.safeAreaInsets.bottom}px`;
|
|
mainEl.style.paddingLeft = `${ctx.safeAreaInsets.left}px`;
|
|
}
|
|
}
|
|
|
|
const app = new App({ name: "Lazy Auth Demo (Secret)", version: "1.0.0" });
|
|
|
|
app.onerror = console.error;
|
|
app.onhostcontextchanged = handleHostContextChanged;
|
|
|
|
app.ontoolresult = (result) => {
|
|
const text =
|
|
result.content?.find((c) => c.type === "text")?.text ?? "(no content)";
|
|
const div = document.createElement("div");
|
|
div.className = result.isError ? "error" : "result";
|
|
div.textContent = text;
|
|
outputEl.replaceChildren(div);
|
|
};
|
|
|
|
app.connect().then(() => {
|
|
const ctx = app.getHostContext();
|
|
if (ctx) {
|
|
handleHostContextChanged(ctx);
|
|
}
|
|
});
|