Skip to content

Commit bb5cd8c

Browse files
committed
Split styling into dedicated plugin, fix auth terminology, improve breadcrumbs
Extract CSS cascade fix, breadcrumb labels, and sidebar nav highlighting from the SEO plugin into a new typedoc-plugin-mcpstyle.mjs to keep concerns separated. Fix authorization doc: use "authorization" consistently instead of mixing with "authentication", link to MCP spec and RFC 9728 for Protected Resource Metadata, remove redundant spec link paragraph. Rename "Documents" nav section to "Getting Started" and add explicit group frontmatter to all doc pages. Breadcrumbs now show the section name instead of duplicating the page title.
1 parent 19e50f7 commit bb5cd8c

10 files changed

Lines changed: 80 additions & 46 deletions

docs/agent-skills.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
title: Agent Skills
3+
group: Getting Started
34
description: Use Agent Skills to build, migrate, and extend MCP Apps with AI coding agents. Install skills that scaffold new apps, convert OpenAI Apps, and add UI to existing servers.
45
---
56

docs/authorization.md

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,20 @@ description: Learn how to protect MCP App tools with OAuth authorization, includ
66

77
# Authorization
88

9-
MCP Apps can protect tools behind OAuth authorization. There are two approaches:
9+
MCP Apps can protect tools behind OAuth-based authorization, as defined in the [MCP specification](https://modelcontextprotocol.io/specification/latest/basic/authorization). There are two approaches:
1010

11-
- **Per-server authorization** — The entire MCP server requires authentication at connection time. Every request must include a valid token, regardless of which tool is being called. This is the simpler model when all tools are sensitive.
12-
- **Per-tool authorization** — Only specific tools require authentication. Public tools work without a token, and the OAuth flow is triggered only when the user calls a protected tool. This lets you mix public and protected tools in the same server.
13-
14-
Both approaches use the same underlying mechanism: HTTP `401` responses with [Protected Resource Metadata](https://modelcontextprotocol.io/specification/latest/basic/authorization#authorization-server-location). The difference is _when_ the `401` is returned — on every unauthenticated request, or only when a protected tool is called.
15-
16-
For the full protocol specification — including OAuth 2.1 requirements, discovery mechanisms, client registration approaches, token handling, and security considerations — see the [MCP Authorization specification](https://modelcontextprotocol.io/specification/latest/basic/authorization).
11+
- **Per-server authorization** — The entire MCP server requires authorization at connection time. Every request must include a valid token, regardless of which tool is being called. This is the simpler model when all tools are sensitive.
12+
- **Per-tool authorization** — Only specific tools require authorization. Public tools work without a token, and the OAuth flow is triggered only when the user calls a protected tool. This lets you mix public and protected tools in the same server.
1713

1814
## Shared setup
1915

2016
Regardless of which approach you choose, you need OAuth discovery metadata and token verification. These are the same for both.
2117

2218
### OAuth discovery metadata
2319

24-
The MCP specification requires servers to implement [authorization server discovery](https://modelcontextprotocol.io/specification/latest/basic/authorization#authorization-server-discovery) so clients know how to authenticate. Two well-known endpoints are needed:
20+
The MCP specification requires servers to implement [authorization server discovery](https://modelcontextprotocol.io/specification/latest/basic/authorization#authorization-server-discovery) so clients know how to obtain authorization. Two well-known endpoints are needed:
2521

26-
**Protected Resource Metadata** (`/.well-known/oauth-protected-resource`) — tells clients where to find the Authorization Server. The MCP SDK's `mcpAuthRouter` handles this automatically.
22+
**[Protected Resource Metadata](https://datatracker.ietf.org/doc/html/rfc9728)** (`/.well-known/oauth-protected-resource`) — describes the resource server and identifies which authorization server(s) can issue tokens for it. The MCP SDK's `mcpAuthRouter` handles this automatically.
2723

2824
**Authorization Server Metadata** (`/.well-known/oauth-authorization-server`) — advertises the authorization and token endpoints, supported scopes, and whether [Client ID Metadata Documents](https://modelcontextprotocol.io/specification/latest/basic/authorization#client-id-metadata-documents) (CIMD) is supported:
2925

@@ -56,13 +52,13 @@ MCP servers must validate that tokens were issued specifically for them — see
5652

5753
## Per-server authorization
5854

59-
With per-server authorization, every request to the `/mcp` endpoint must include a valid Bearer token. Any unauthenticated request receives HTTP `401`, and the host must complete the OAuth flow before the client can use any tools. This is the right choice when all tools are sensitive and there's no value in allowing unauthenticated access.
55+
With per-server authorization, every request to the `/mcp` endpoint must include a valid Bearer token. Any unauthorized request receives HTTP `401`, and the host must complete the OAuth flow before the client can use any tools. This is the right choice when all tools are sensitive and there's no value in allowing unauthorized access.
6056

6157
The TypeScript MCP SDK supports this out of the box via `mcpAuthRouter` and `ProxyOAuthServerProvider` — no custom HTTP handler logic is needed. See the [MCP SDK documentation](https://github.com/modelcontextprotocol/typescript-sdk) for setup details.
6258

6359
## Per-tool authorization
6460

65-
With per-tool authorization, the `/mcp` endpoint handler inspects the raw JSON-RPC request body, checks whether any message targets a protected tool, and only enforces authentication for those calls. Public tools pass through without a token.
61+
With per-tool authorization, the `/mcp` endpoint handler inspects the raw JSON-RPC request body, checks whether any message targets a protected tool, and only enforces authorization for those calls. Public tools pass through without a token.
6662

6763
### How it works
6864

@@ -146,7 +142,7 @@ The `WWW-Authenticate` header includes the [Protected Resource Metadata](https:/
146142

147143
### Defence-in-depth in tool handlers
148144

149-
Even though the HTTP layer enforces authorization, protected tool handlers should also verify `authInfo` as a defence-in-depth measure. If the HTTP layer is misconfigured or bypassed, the tool handler catches it:
145+
Even though the HTTP layer enforces authorization, protected tool handlers should also verify `authInfo` as a defence-in-depth measure. If the HTTP layer is misconfigured or bypassed, the tool handler catches unauthorized access:
150146

151147
```ts
152148
registerAppTool(
@@ -163,7 +159,7 @@ registerAppTool(
163159
content: [
164160
{
165161
type: "text",
166-
text: "Authentication required to access account data.",
162+
text: "Authorization required to access account data.",
167163
},
168164
],
169165
};
@@ -179,12 +175,12 @@ registerAppTool(
179175

180176
### UI-initiated auth escalation
181177

182-
A powerful pattern is mixing public and protected tools in the same app. The app loads with public data (no auth required), and authentication is triggered only when the user performs a protected action. This is a practical application of the [step-up authorization flow](https://modelcontextprotocol.io/specification/latest/basic/authorization#step-up-authorization-flow) described in the spec:
178+
A powerful pattern is mixing public and protected tools in the same app. The app loads with public data (no authorization required), and the OAuth flow is triggered only when the user performs a protected action. This is a practical application of the [step-up authorization flow](https://modelcontextprotocol.io/specification/latest/basic/authorization#step-up-authorization-flow) described in the spec:
183179

184-
1. A public tool (e.g., `manage_branch`) loads the UI with unauthenticated data
180+
1. A public tool (e.g., `manage_branch`) loads the UI without requiring authorization
185181
2. The user clicks a button that calls a protected tool via `app.callServerTool()`
186182
3. The MCP host receives HTTP `401` and automatically runs the OAuth flow
187-
4. After the user authenticates, the host retries the tool call with the new token
183+
4. After the user completes the OAuth flow, the host retries the tool call with the acquired token
188184
5. The protected data appears in the UI
189185

190186
```tsx
@@ -193,7 +189,7 @@ function BranchItem({ branch }: { branch: Branch }) {
193189

194190
async function handleManage() {
195191
// This call may trigger the OAuth flow if the user
196-
// hasn't authenticated yet — the host handles it
192+
// hasn't been authorized yet — the host handles it
197193
// transparently.
198194
const result = await app.callServerTool({
199195
name: "manage_branch_admin",
@@ -212,4 +208,4 @@ function BranchItem({ branch }: { branch: Branch }) {
212208
}
213209
```
214210

215-
This pattern keeps the initial experience fast (no login wall) while securing sensitive operations behind authentication. The host manages the entire OAuth flow — the app code simply calls the tool and handles the result.
211+
This pattern keeps the initial experience fast (no login wall) while securing sensitive operations behind authorization. The host manages the entire OAuth flow — the app code simply calls the tool and handles the result.

docs/migrate_from_openai_apps.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
title: Migrate OpenAI App
3+
group: Getting Started
34
description: Migrate from the OpenAI Apps SDK to MCP Apps SDK with concept mapping tables, API equivalents, and complete before/after code examples.
45
---
56

docs/overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
title: Overview
3+
group: Getting Started
34
description: MCP Apps extends the Model Context Protocol to let MCP servers deliver interactive UIs — charts, forms, dashboards — rendered securely in iframes inside any compliant host.
45
---
56

docs/patterns.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
title: Patterns
3+
group: Getting Started
34
description: Common patterns and recipes for building MCP Apps — polling, chunked data, binary resources, theming, fullscreen, model context, state persistence, and more.
45
---
56

docs/quickstart.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
title: Quickstart
3+
group: Getting Started
34
description: Build your first MCP App step by step — create an MCP server with an interactive View that renders inside Claude Desktop and other MCP hosts.
45
---
56

docs/testing-mcp-apps.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
title: Testing MCP Apps
3+
group: Getting Started
34
description: Test MCP Apps locally with the basic-host reference implementation or in production hosts like Claude.ai and VS Code.
45
---
56

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* TypeDoc plugin that applies MCP-specific styling tweaks.
3+
*
4+
* - Moves custom.css to load last so overrides win the cascade
5+
* - Replaces breadcrumbs with the document group name (e.g. "Security")
6+
* - Marks the current sidebar nav link for CSS highlighting
7+
*/
8+
9+
import { Renderer } from "typedoc";
10+
11+
/**
12+
* TypeDoc plugin entry point.
13+
* @param {import('typedoc').Application} app
14+
*/
15+
export function load(app) {
16+
app.renderer.on(Renderer.EVENT_END_PAGE, (page) => {
17+
if (!page.contents) return;
18+
19+
// Move custom.css to load after all theme stylesheets so overrides win the cascade
20+
const customCssLink = page.contents.match(
21+
/<link rel="stylesheet" href="[^"]*custom\.css"\/>/,
22+
);
23+
if (customCssLink) {
24+
page.contents = page.contents.replace(customCssLink[0], "");
25+
page.contents = page.contents.replace(
26+
"</head>",
27+
customCssLink[0] + "\n</head>",
28+
);
29+
}
30+
31+
// For document pages, replace the breadcrumb with the group name
32+
// (e.g. "Security", "Getting Started"). The page title is in the H1.
33+
if (page.model?.isDocument?.() && page.model.frontmatter?.group) {
34+
const group = String(page.model.frontmatter.group);
35+
page.contents = page.contents.replace(
36+
/<ul class="tsd-breadcrumb"[^>]*>.*?<\/ul>/,
37+
`<ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><span>${group}</span></li></ul>`,
38+
);
39+
}
40+
41+
// Inject script to mark the current sidebar nav link with a "current" class.
42+
// TypeDoc does not natively add this class for document pages.
43+
// The sidebar is populated asynchronously from compressed navigation data,
44+
// so we use a MutationObserver to detect when links appear.
45+
// Pathname comparison strips trailing slashes and .html extensions to handle
46+
// servers with clean-URL mode (e.g. `serve` drops .html).
47+
const currentNavScript = `<script>(function(){function norm(s){return s.replace(/\\/$/,"").replace(/\\.html$/,"");}function mark(){var p=norm(location.pathname);var links=document.querySelectorAll(".site-menu .tsd-navigation a[href]");for(var i=0;i<links.length;i++){var h=norm(new URL(links[i].href,location.href).pathname);if(h===p){links[i].classList.add("current");return true;}}return false;}function init(){if(!mark()){var c=document.getElementById("tsd-nav-container");if(c){new MutationObserver(function(m,o){if(mark())o.disconnect();}).observe(c,{childList:true,subtree:true});}}}if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",init);}else{init();}})();</script>`;
48+
page.contents = page.contents.replace(
49+
"</body>",
50+
currentNavScript + "\n</body>",
51+
);
52+
});
53+
}

scripts/typedoc-plugin-seo.mjs

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* - Normalizes document page filenames to lowercase-hyphenated slugs
55
* - Injects JSON-LD (TechArticle / WebPage) structured data for search crawlers
66
* - Adds per-page meta description tags extracted from page content
7+
* - Copies favicons to the output directory
78
*/
89

910
import { Renderer } from "typedoc";
@@ -116,7 +117,7 @@ function buildJsonLd({ title, description, url, isDocument }) {
116117
export function load(app) {
117118
const hostedBaseUrl = app.options.getValue("hostedBaseUrl") || "";
118119

119-
// --- Per-page: inject JSON-LD and meta descriptions ---
120+
// --- Per-page: inject JSON-LD, meta descriptions, and favicons ---
120121
app.renderer.on(Renderer.EVENT_END_PAGE, (page) => {
121122
if (!page.contents) return;
122123

@@ -168,39 +169,15 @@ export function load(app) {
168169
`<link rel="apple-touch-icon" href="${base}favicons/apple-touch-icon.png" type="image/png" sizes="180x180"/>`,
169170
].join("\n");
170171

171-
// Move custom.css to load after all theme stylesheets so overrides win the cascade
172-
const customCssLink = page.contents.match(
173-
/<link rel="stylesheet" href="[^"]*custom\.css"\/>/,
174-
);
175-
if (customCssLink) {
176-
page.contents = page.contents.replace(customCssLink[0], "");
177-
}
178-
179-
// Inject favicons, relocated custom CSS, and JSON-LD before </head>
180-
const headInjections = [
181-
faviconTags,
182-
customCssLink ? customCssLink[0] : "",
183-
jsonLdScript,
184-
]
172+
// Inject favicons and JSON-LD before </head>
173+
const headInjections = [faviconTags, jsonLdScript]
185174
.filter(Boolean)
186175
.join("\n");
187176

188177
page.contents = page.contents.replace(
189178
"</head>",
190179
headInjections + "\n</head>",
191180
);
192-
193-
// Inject script to mark the current sidebar nav link with a "current" class.
194-
// TypeDoc does not natively add this class for document pages.
195-
// The sidebar is populated asynchronously from compressed navigation data,
196-
// so we use a MutationObserver to detect when links appear.
197-
// Pathname comparison strips trailing slashes and .html extensions to handle
198-
// servers with clean-URL mode (e.g. `serve` drops .html).
199-
const currentNavScript = `<script>(function(){function norm(s){return s.replace(/\\/$/,"").replace(/\\.html$/,"");}function mark(){var p=norm(location.pathname);var links=document.querySelectorAll(".site-menu .tsd-navigation a[href]");for(var i=0;i<links.length;i++){var h=norm(new URL(links[i].href,location.href).pathname);if(h===p){links[i].classList.add("current");return true;}}return false;}function init(){if(!mark()){var c=document.getElementById("tsd-nav-container");if(c){new MutationObserver(function(m,o){if(mark())o.disconnect();}).observe(c,{childList:true,subtree:true});}}}if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",init);}else{init();}})();</script>`;
200-
page.contents = page.contents.replace(
201-
"</body>",
202-
currentNavScript + "\n</body>",
203-
);
204181
});
205182

206183
// --- Post-render: copy favicons + rename document slugs ---

typedoc.config.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const config = {
3535
},
3636
includeVersion: false,
3737
categorizeByGroup: true,
38-
groupOrder: ["Documents", "Security", "Modules", "*"],
38+
groupOrder: ["Getting Started", "Security", "Modules", "*"],
3939
navigation: {
4040
includeGroups: true,
4141
},
@@ -51,11 +51,13 @@ const config = {
5151
"typedoc-github-theme",
5252
"./scripts/typedoc-plugin-fix-mermaid-entities.mjs",
5353
"./scripts/typedoc-plugin-seo.mjs",
54+
"./scripts/typedoc-plugin-mcpstyle.mjs",
5455
"@boneskull/typedoc-plugin-mermaid",
5556
],
5657
ignoredHighlightLanguages: ["mermaid"],
5758
locales: {
5859
en: {
60+
kind_plural_document: "Getting Started",
5961
kind_plural_module: "API Documentation",
6062
},
6163
},

0 commit comments

Comments
 (0)