diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
index bfc7f07e5..1a1017092 100644
--- a/.github/workflows/labeler.yml
+++ b/.github/workflows/labeler.yml
@@ -11,6 +11,6 @@ jobs:
label:
runs-on: ubuntu-latest
steps:
- - uses: actions/labeler@v6
+ - uses: actions/labeler@v7
with:
sync-labels: false
diff --git a/blog/assets/css/extended/quotes.css b/blog/assets/css/extended/quotes.css
new file mode 100644
index 000000000..f081b9fb8
--- /dev/null
+++ b/blog/assets/css/extended/quotes.css
@@ -0,0 +1,256 @@
+/* ─── Quote cards (quotes / quote shortcodes) ────────────────────────────── */
+/* A row of testimonial cards separated by hairline dividers. The track is a
+ scroll-snap carousel; nav controls are injected by the shortcode's script
+ and stay hidden when everything fits one view. */
+
+/* The strip breaks out of the prose column: symmetric negative margins grow
+ it toward the viewport edges, capped at 1200px so it stays sane on wide
+ monitors. The viewport bound is 50vw - 50% - 32px per side. 100vw includes
+ the page scrollbar, so the 32px gutter both absorbs the scrollbar width
+ and keeps a visible margin, which is what prevents a page-level horizontal
+ scrollbar. The prose column is centered in the viewport, so symmetric
+ margins keep the strip centered too. */
+.mcp-quotes {
+ --mcp-quotes-bleed: max(0px, min((1200px - 100%) / 2, 50vw - 50% - 32px));
+ margin: 40px calc(-1 * var(--mcp-quotes-bleed));
+}
+
+/* A lone quote is a pull quote, not a strip: it stays in the prose column. */
+.mcp-quotes:has(.mcp-quote:only-child) {
+ --mcp-quotes-bleed: 0px;
+}
+
+.mcp-quotes-track {
+ display: grid;
+ grid-auto-flow: column;
+ grid-auto-columns: 100%;
+ overflow-x: auto;
+ scroll-snap-type: x mandatory;
+ overscroll-behavior-x: contain;
+ scrollbar-width: none;
+}
+
+.mcp-quotes-track::-webkit-scrollbar {
+ display: none;
+}
+
+/* When more cards exist than fit one view, they are sized a bit narrower so
+ a slice of the next card peeks at the right edge, signaling that the strip
+ scrolls. Sets that fit exactly (1, 2, or 3 cards per breakpoint) keep the
+ full division and no peek. */
+@media (max-width: 599px) {
+ .mcp-quotes-track:has(> .mcp-quote + .mcp-quote) {
+ grid-auto-columns: calc(100% - 44px);
+ }
+}
+
+@media (min-width: 600px) {
+ .mcp-quotes-track {
+ grid-auto-columns: 50%;
+ }
+}
+
+@media (min-width: 600px) and (max-width: 899px) {
+ .mcp-quotes-track:has(> .mcp-quote:nth-child(3)) {
+ grid-auto-columns: calc((100% - 56px) / 2);
+ }
+}
+
+@media (min-width: 900px) {
+ .mcp-quotes-track {
+ grid-auto-columns: calc(100% / 3);
+ }
+
+ .mcp-quotes-track:has(> .mcp-quote:nth-child(4)) {
+ grid-auto-columns: calc((100% - 72px) / 3);
+ }
+}
+
+.post-content .mcp-quote {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ margin: 0;
+ padding: 8px 24px;
+ border-inline-start: 1px solid var(--border);
+ scroll-snap-align: start;
+}
+
+.post-content .mcp-quote:first-child {
+ padding-inline-start: 0;
+ border-inline-start: none;
+}
+
+/* A lone quote reads as a pull quote: full track width, capped measure. */
+.mcp-quotes-track:has(> .mcp-quote:only-child) {
+ grid-auto-columns: 100%;
+}
+
+.post-content .mcp-quote:only-child {
+ padding-inline-end: 0;
+}
+
+.post-content .mcp-quote:only-child .mcp-quote-text {
+ max-width: 640px;
+}
+
+/* 1-up mobile: tighter padding, but keep the hairline so the peeking next
+ card reads as a separate card. */
+@media (max-width: 599px) {
+ .post-content .mcp-quote {
+ padding-inline: 16px;
+ }
+}
+
+/* Logos render at a common cap height regardless of the SVG's own aspect
+ ratio. Inline SVGs inherit currentColor so they track the theme. */
+.mcp-quote-logo {
+ display: block;
+ height: 28px;
+ margin-bottom: 20px;
+ color: var(--primary);
+}
+
+.mcp-quote-logo svg,
+.mcp-quote-logo img {
+ display: block;
+ height: 100%;
+ width: auto;
+ max-width: 100%;
+}
+
+.mcp-quote-logo-text {
+ font-size: 18px;
+ font-weight: 400;
+ line-height: 28px;
+ letter-spacing: -0.01em;
+ color: var(--primary);
+}
+
+/* Reset PaperMod's blockquote treatment (left bar, italic-ish inset). */
+.post-content .mcp-quote-text {
+ margin: 0;
+ padding: 0;
+ border: none;
+ font-size: 16px;
+ font-style: normal;
+ line-height: 1.6;
+ color: var(--content);
+}
+
+.post-content .mcp-quote-text p {
+ display: inline;
+ margin: 0;
+ font-size: inherit;
+}
+
+/* Attribution: the name bold on one line, the title regular and muted on the
+ next. PaperMod bolds figure > figcaption wholesale, so the figcaption is
+ reset to 400 and only the name span re-bolds (700, matching the blog's
+ strong text). Both lines share one tight line-height so the pair reads as
+ a unit, not two paragraphs. */
+.post-content .mcp-quote-attrib {
+ margin-top: auto;
+ padding-top: 16px;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.45;
+ color: var(--secondary);
+}
+
+.mcp-quote-attrib-name {
+ display: block;
+ font-weight: 700;
+ color: var(--content);
+}
+
+.mcp-quote-attrib-title {
+ display: block;
+}
+
+/* Attributions start on the same line in every card. The track exposes three
+ explicit rows (logo / quote / attribution) and each card adopts them via
+ subgrid, so the quote row is sized by the longest quote in the set and
+ every attribution starts at the same y, on every carousel page. Long
+ quotes spill downward inside that row; short cards keep the open space.
+ Browsers without subgrid keep the flex column above, where attributions
+ bottom-align instead. Skipped under 600px, where cards show one per view
+ and cross-card alignment would only add empty space. */
+@media (min-width: 600px) {
+ @supports (grid-template-rows: subgrid) {
+ .mcp-quotes-track {
+ grid-template-rows: auto auto auto;
+ }
+
+ .post-content .mcp-quote {
+ display: grid;
+ grid-template-rows: subgrid;
+ grid-row: span 3;
+ }
+
+ .post-content .mcp-quote .mcp-quote-attrib {
+ margin-top: 0;
+ align-self: start;
+ }
+ }
+}
+
+/* ─── Carousel controls ──────────────────────────────────────────────────── */
+.mcp-quotes-nav {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 16px;
+ margin-top: 28px;
+}
+
+.mcp-quotes-nav[hidden] {
+ display: none;
+}
+
+.mcp-quotes-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ padding: 0;
+ color: var(--primary);
+ background: var(--code-bg);
+ border: 1px solid var(--border);
+ border-radius: 50%;
+ cursor: pointer;
+ transition: border-color 0.15s ease;
+}
+
+@media (hover: hover) {
+ .mcp-quotes-btn:not(:disabled):hover {
+ border-color: var(--primary);
+ }
+}
+
+.mcp-quotes-btn:disabled {
+ opacity: 0.35;
+ cursor: default;
+}
+
+.mcp-quotes-dots {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.mcp-quotes-dot {
+ width: 8px;
+ height: 8px;
+ padding: 0;
+ background: var(--tertiary);
+ border: none;
+ border-radius: 50%;
+ cursor: pointer;
+ transition: background 0.15s ease;
+}
+
+.mcp-quotes-dot[aria-current="true"] {
+ background: var(--primary);
+}
diff --git a/blog/content/posts/2026-05-21-mcp-2026-07-28-rc.md b/blog/content/posts/2026-05-21-mcp-2026-07-28-rc.md
index 5348f6861..fbc4686ba 100644
--- a/blog/content/posts/2026-05-21-mcp-2026-07-28-rc.md
+++ b/blog/content/posts/2026-05-21-mcp-2026-07-28-rc.md
@@ -145,7 +145,7 @@ change how those prompts are delivered. Instead of holding a Server-Sent Events
```json
{
- "resultType": "inputRequired",
+ "resultType": "input_required",
"inputRequests": {
"confirm": {
"type": "elicitation",
diff --git a/blog/layouts/shortcodes/quote.html b/blog/layouts/shortcodes/quote.html
new file mode 100644
index 000000000..c2516bd29
--- /dev/null
+++ b/blog/layouts/shortcodes/quote.html
@@ -0,0 +1,39 @@
+{{- /*
+ A single testimonial card. Use inside a `quotes` block.
+
+ Params:
+ name - person's name
+ title - person's role, shown on its own line under the name
+ company - company name, used as the logo's accessible label
+ (and shown as text when no logo is given)
+ logo - filename of an SVG in the post's page bundle, or a static path.
+ Bundle SVGs are inlined so currentColor tracks the theme.
+
+ The quote text goes in the shortcode body. Markdown is supported.
+*/ -}}
+{{- $name := .Get "name" -}}
+{{- $title := .Get "title" -}}
+{{- $company := .Get "company" -}}
+{{- $logo := .Get "logo" -}}
+
+ {{- with $logo -}}
+ {{- with $.Page.Resources.GetMatch . -}}
+ {{- if eq .MediaType.SubType "svg" -}}
+ {{ .Content | safeHTML }}
+ {{- else -}}
+
+ {{- end -}}
+ {{- else -}}
+
+ {{- end -}}
+ {{- else -}}
+ {{ $company }}
+ {{- end }}
+ “{{ trim .Inner " \n" | .Page.RenderString }}”
+
+ {{ $name }}
+ {{- with $title }}
+ {{ . }}
+ {{- end }}
+
+
diff --git a/blog/layouts/shortcodes/quotes.html b/blog/layouts/shortcodes/quotes.html
new file mode 100644
index 000000000..76920767b
--- /dev/null
+++ b/blog/layouts/shortcodes/quotes.html
@@ -0,0 +1,107 @@
+{{- /*
+ Testimonial quote row. Wraps one or more inner `quote` shortcodes in a
+ scroll-snap carousel. Nav controls stay hidden unless the quotes overflow
+ one view, so 1-3 quotes render as a plain static row.
+
+ Usage:
+ {{< quotes >}}
+ {{< quote name="Ada Person" title="CTO" company="Acme" logo="acme.svg" >}}
+ Quote text, markdown allowed.
+ {{< /quote >}}
+ {{< /quotes >}}
+*/ -}}
+{{- $label := .Get "label" | default "Testimonials" -}}
+
+{{- if not (.Page.Store.Get "mcp-quotes-js") -}}
+{{- .Page.Store.Set "mcp-quotes-js" true }}
+
+{{- end -}}
diff --git a/docs/community/working-groups/mcp-apps.mdx b/docs/community/working-groups/mcp-apps.mdx
new file mode 100644
index 000000000..c1c53160c
--- /dev/null
+++ b/docs/community/working-groups/mcp-apps.mdx
@@ -0,0 +1,183 @@
+---
+title: MCP Apps Charter
+description: Charter for the MCP Apps Working Group.
+---
+
+## Group Type
+
+**Working Group**
+
+## Mission Statement
+
+The MCP Apps Working Group defines how MCP servers deliver interactive user
+interfaces to hosts. The group emerged from the convergence of MCP-UI and the
+OpenAI Apps SDK into a single open standard via
+[SEP-1865](/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp)
+(Extensions Track, Final), which introduces predeclared UI resources (the
+`ui://` URI scheme), tool-to-UI association through metadata, and
+bi-directional host/UI communication over MCP's JSON-RPC base protocol. The WG
+produces specification extensions, SDKs, reference implementations, and host
+guidance because interactive UI touches the protocol spec, SDK surface, host
+security model, and client rendering behavior across the ecosystem (Claude,
+ChatGPT, VS Code, Goose, Postman, and others).
+
+## Scope
+
+### In Scope
+
+- **Specification Work**: The MCP Apps Extension
+ ([modelcontextprotocol/ext-apps](https://github.com/modelcontextprotocol/ext-apps))
+ and related SEPs, including UI resource declaration, tool metadata, lifecycle
+ and messaging (`ui/*` methods and notifications), and content-type profiles
+ (initially `text/html;profile=mcp-app`) under the
+ `io.modelcontextprotocol/ui` namespace.
+- **Future Content Types & Rendering Targets**: Non-HTML view mimetypes,
+ container-agnostic rendering, and declarative or generative UI directions,
+ including A2UI interoperability.
+- **SDKs & Reference Implementations**: The MCP Apps SDK
+ (`@modelcontextprotocol/ext-apps`), example servers, the reference host, and
+ host integration references demonstrating secure rendering and
+ bi-directional communication.
+- **Security Model**: Sandboxing, origin isolation, CSP guidance, auditability
+ of UI-initiated actions, and threat-model documentation for host
+ implementers.
+- **Conformance**: Host compatibility tests, an official MCP App validator, and
+ the definition of what "spec-conformant" means for both apps and hosts.
+- **Cross-Cutting Concerns**: Coordination with the Registry WG (app
+ discoverability and metadata), Agents WG (UI in agentic loops), Security IG
+ (sandboxing and UI-initiated tool calls), SDK WG and SDK maintainers
+ (per-language support), and external projects (A2UI, AG-UI, and community
+ frameworks).
+- **Documentation**: Specification sections and guidance for app authors and
+ host implementers.
+
+### Out of Scope
+
+- **Host-specific design systems**: Hosts control final look-and-feel. The WG
+ standardizes the contract, not visual design.
+- **Client implementation mandates**: We document rendering and security
+ patterns but cannot require specific host behavior beyond spec conformance.
+- **General agent orchestration**: Agent loops and sub-agent coordination
+ belong to the Agents WG. This WG covers only the UI surface.
+- **Core protocol changes**: Changes to base MCP semantics are proposed through
+ the [SEP process](/community/sep-guidelines) and owned by the relevant group
+ or Core Maintainers. This WG owns the Apps extension, not the base protocol.
+
+### Related Groups
+
+- **Agents WG**: interactive UI within agentic flows and task lifecycles.
+- **[Registry WG](/community/working-groups/registry)**: app metadata,
+ discovery, and distribution.
+- **[Security IG](/community/interest-groups/security)**: sandboxing model and
+ review of UI-initiated actions.
+- **[SDK WG](/community/working-groups/sdk)**: per-language MCP Apps support
+ and SDK consistency.
+
+## Leadership
+
+| Role | Name | Organization | GitHub | Term |
+| ---- | ----------- | ------------ | ------------------------------------ | ------- |
+| Lead | Ido Salomon | MCP-UI | [@idosal](https://github.com/idosal) | Initial |
+| Lead | Liad Yosef | MCP-UI / Ora | [@liady](https://github.com/liady) | Initial |
+
+## Authority & Decision Rights
+
+| Decision Type | Authority Level |
+| ----------------------------------- | ------------------------------------------------------ |
+| Meeting logistics & scheduling | WG Leads (autonomous) |
+| Proposal prioritization within WG | WG Leads (autonomous) |
+| SEP triage & closure (in scope) | WG Leads (autonomous, with documented rationale) |
+| Technical design within scope | WG consensus |
+| Spec changes (additive) | WG consensus → Core Maintainer approval |
+| Spec changes (breaking/fundamental) | WG consensus → Core Maintainer approval + wider review |
+| Scope expansion | Core Maintainer approval required |
+| WG Member approval | WG Member sponsors |
+
+## Membership
+
+| Name | Organization | GitHub | Discord | Level |
+| ------------------ | -------------------------- | ---------------------------------------------------------- | ------- | ----------- |
+| Ido Salomon | MCP-UI | [@idosal](https://github.com/idosal) | | Lead |
+| Liad Yosef | MCP-UI / Ora | [@liady](https://github.com/liady) | | Lead |
+| Olivier Chafik | Anthropic / MCP Maintainer | [@ochafik](https://github.com/ochafik) | | WG Member |
+| Jonathan Hefner | MCP Maintainer | [@jonathanhefner](https://github.com/jonathanhefner) | | WG Member |
+| Anton Pidkuiko | MCP Maintainer | [@antonpk1](https://github.com/antonpk1) | | WG Member |
+| Nick Cooper | OpenAI | [@nickcoai](https://github.com/nickcoai) | | WG Member |
+| Max Stoiber | OpenAI | [@mstoiber-oai](https://github.com/mstoiber-oai) | | Participant |
+| Dominic Farolino | Google / Chromium | [@domfarolino](https://github.com/domfarolino) | | Participant |
+| Cameron Yick | Datadog | [@hydrosquall](https://github.com/hydrosquall) | | Participant |
+| Frédéric Barthelet | Alpic | [@fredericbarthelet](https://github.com/fredericbarthelet) | | Participant |
+| Ola Hungerford | Nordstrom | [@olaservo](https://github.com/olaservo) | | Participant |
+| Yann Jouanin | | [@yannj-fr](https://github.com/yannj-fr) | | Participant |
+
+## Operations
+
+| Meeting | Frequency | Duration | Purpose |
+| --------------- | -------------------------------- | -------- | ------------------------------------------------------------------------ |
+| Working Session | Tri-weekly (Wednesday, 08:00 PT) | 60 min | Spec review, SDK coordination, host implementation feedback; open to all |
+| Office Hours | Monthly (planned) | 30 min | Open Q&A for app authors, host implementers, and newcomers |
+
+Meetings are published at
+[meet.modelcontextprotocol.io](https://meet.modelcontextprotocol.io) under the
+`#mcp-apps-wg` tag. Agendas and notes are maintained as a public rolling
+document, the
+[MCP Apps Workgroup Agenda](https://docs.google.com/document/d/103S6IjN9D80ntbPIcK3mP_ZWdeFom2Mq8qpkpIsXRVI/edit),
+and are posted per the MCP meeting policy. The group has met since
+2025-12-17.
+
+Discord: `#mcp-apps-wg`
+
+## Resources
+
+- Extension specification & SDK:
+ [modelcontextprotocol/ext-apps](https://github.com/modelcontextprotocol/ext-apps)
+ (npm: `@modelcontextprotocol/ext-apps`)
+- SEP: [SEP-1865](/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp)
+- Ecosystem docs: [MCP Apps overview](/extensions/apps/overview) and
+ [Build an MCP App](/extensions/apps/build)
+- Documentation & API reference:
+ [apps.extensions.modelcontextprotocol.io](https://apps.extensions.modelcontextprotocol.io/)
+- Issue tracker:
+ [ext-apps issues](https://github.com/modelcontextprotocol/ext-apps/issues)
+
+## Deliverables & Success Metrics
+
+### Active Work Items
+
+Live tracking is in
+[ext-apps issues](https://github.com/modelcontextprotocol/ext-apps/issues) and
+pull requests. Headline workstreams:
+
+| Item | Status | Target Date | Champion |
+| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ----------- | -------------------------------------------------------------------------------------- |
+| Spec maintenance & next revision (current: 2026-01-26) | In Progress | | [@idosal](https://github.com/idosal), [@liady](https://github.com/liady) |
+| MCP Apps SDK (`@modelcontextprotocol/ext-apps`, stable 1.x) | Maintained | | MCP Apps SDK maintainers |
+| Host compatibility & conformance test suite ([#674](https://github.com/modelcontextprotocol/ext-apps/issues/674)) | In Progress | | [@idosal](https://github.com/idosal) |
+| Reusable views & view session identity ([#430](https://github.com/modelcontextprotocol/ext-apps/issues/430)) | In Progress | | [@mstoiber-oai](https://github.com/mstoiber-oai), [@idosal](https://github.com/idosal) |
+| Rendering architecture: decouple requirements from the double-sandboxed iframe ([#716](https://github.com/modelcontextprotocol/ext-apps/issues/716)) | In Review | | [@domfarolino](https://github.com/domfarolino) |
+| Dynamic View Content via embedded resources ([#699](https://github.com/modelcontextprotocol/ext-apps/pull/699)) | Proposal | | [@liady](https://github.com/liady) |
+| Views not tied to a specific tool call ([#672](https://github.com/modelcontextprotocol/ext-apps/issues/672)) | In Progress | | [@mstoiber-oai](https://github.com/mstoiber-oai), [@liady](https://github.com/liady) |
+| Authoring toolkit: Agent Skills, reference host (`basic-host`), example suite | Maintained | | TBD |
+
+### Success Criteria
+
+- Published, stable extension specification (2026-01-26 revision shipped) with
+ a maintained SDK and reference host, and responsive triage of spec and SDK
+ issues.
+- A conformance suite that host implementers can run, with results published so
+ app authors know what works where.
+- Interoperable MCP Apps running unmodified across multiple major hosts, with
+ the documented security model adopted by those hosts.
+- A clear, specified path for content types beyond HTML.
+- MCP Apps established as the standard UI layer of the agentic web: a single
+ app implementation reaching every MCP host.
+
+## Changelog
+
+| Date | Change |
+| ---------- | ------------------------------------------------------------------------------------------ |
+| 2025-11-21 | SEP-1865 proposed; MCP-UI and OpenAI Apps SDK approaches unified under the MCP Apps banner |
+| 2025-12-17 | First MCP Apps Working Group meeting |
+| 2026-01-26 | MCP Apps extension specification revision 2026-01-26 published |
+| 2026-01-28 | SEP-1865 marked Final and merged |
+| 2026-07-26 | Initial charter |
diff --git a/docs/docs.json b/docs/docs.json
index 74bcb9caa..0360e197c 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -497,6 +497,7 @@
"community/working-groups/file-uploads",
"community/working-groups/inspector-v2",
"community/working-groups/interceptors",
+ "community/working-groups/mcp-apps",
"community/working-groups/registry",
"community/working-groups/sdk",
"community/working-groups/server-card",
diff --git a/docs/docs/tutorials/security/authorization.mdx b/docs/docs/tutorials/security/authorization.mdx
index 572d3e806..6de531d04 100644
--- a/docs/docs/tutorials/security/authorization.mdx
+++ b/docs/docs/tutorials/security/authorization.mdx
@@ -648,8 +648,8 @@ class Config:
AUTH_REALM: str = os.getenv("AUTH_REALM", "master")
# OAuth client settings
- OAUTH_CLIENT_ID: str = os.getenv("OAUTH_CLIENT_ID", "mcp-server")
- OAUTH_CLIENT_SECRET: str = os.getenv("OAUTH_CLIENT_SECRET", "UO3rmozkFFkXr0QxPTkzZ0LMXDidIikB")
+ OAUTH_CLIENT_ID: str = os.getenv("OAUTH_CLIENT_ID", "test-client")
+ OAUTH_CLIENT_SECRET: str = os.getenv("OAUTH_CLIENT_SECRET", "")
# Server settings
MCP_SCOPE: str = os.getenv("MCP_SCOPE", "mcp:tools")
@@ -869,8 +869,11 @@ class IntrospectionTokenVerifier(TokenVerifier):
form_data = {
"token": token,
"client_id": self.client_id,
- "client_secret": self.client_secret,
}
+ # Only send client_secret when one is configured
+ # Public clients authenticate with client_id alone.
+ if self.client_secret:
+ form_data["client_secret"] = self.client_secret
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = await client.post(
@@ -894,7 +897,13 @@ class IntrospectionTokenVerifier(TokenVerifier):
client_id=data.get("client_id", "unknown"),
scopes=data.get("scope", "").split() if data.get("scope") else [],
expires_at=data.get("exp"),
- resource=data.get("aud"), # Include resource in token
+ # AccessToken.resource is `str | None`. Keycloak returns `aud`
+ # as a *list* here (e.g. ["test-client", "http://localhost:3000",
+ # "account"]); passing that list straight in raises a pydantic
+ # ValidationError that the broad `except` below turns into a
+ # silent 401. We already confirmed this server's resource is a
+ # valid audience in `_validate_resource`, so record that.
+ resource=self.resource_url,
)
except Exception as e:
@@ -923,7 +932,51 @@ class IntrospectionTokenVerifier(TokenVerifier):
return check_resource_allowed(self.resource_url, resource)
```
-For more details, see the [Python SDK documentation](https://github.com/modelcontextprotocol/python-sdk).
+For more details, see below or the [Python SDK documentation](https://github.com/modelcontextprotocol/python-sdk).
+
+**Python MCP Server**
+
+In the server's root have a `pyproject.toml` file and a `mcp_server` folder. Put all the Python files in the `mcp_server` folder, and fill the `pyproject.toml` file like:
+
+```toml
+[project]
+name = "mcp-simple-auth"
+version = "0.1.0"
+description = "A simple MCP server demonstrating OAuth authentication"
+requires-python = ">=3.10"
+authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
+license = { text = "MIT" }
+dependencies = [
+ "anyio>=4.5",
+ "click>=8.2.0",
+ "httpx>=0.27",
+ "mcp",
+ "pydantic>=2.0",
+ "pydantic-settings>=2.5.2",
+ "sse-starlette>=1.6.1",
+ "uvicorn>=0.23.1; sys_platform != 'emscripten'",
+]
+
+[project.scripts]
+mcp-simple-auth-rs = "mcp_server.server:main"
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.hatch.build.targets.wheel]
+packages = ["mcp_server"]
+
+[dependency-groups]
+dev = ["pyright>=1.1.391", "pytest>=8.3.4", "ruff>=0.8.5"]
+```
+
+Then run the commands below to start the server.
+
+```bash
+uv sync
+uv run mcp-simple-auth-rs --port=3000 --auth-server=http://localhost:8080 --transport=streamable-http
+```
diff --git a/package-lock.json b/package-lock.json
index 3d05de693..0bee3de3f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,16 +13,16 @@
"ajv": "^8.20.0",
"ajv-formats": "^3.0.1",
"cheerio": "^1.2.0",
- "eslint": "^10.7.0",
+ "eslint": "^10.8.0",
"eslint-config-prettier": "^10.1.8",
"glob": "^13.0.6",
- "prettier": "^3.9.5",
+ "prettier": "^3.9.6",
"remark-mdx": "^3.1.1",
"remark-parse": "^11.0.0",
"tsx": "^4.23.1",
"typedoc": "^0.28.20",
"typescript": "^6.0.3",
- "typescript-eslint": "^8.64.0",
+ "typescript-eslint": "^8.65.0",
"typescript-json-schema": "0.68.0",
"unified": "^11.0.5"
},
@@ -543,9 +543,9 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz",
- "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==",
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz",
+ "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -870,17 +870,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz",
- "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz",
+ "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.64.0",
- "@typescript-eslint/type-utils": "8.64.0",
- "@typescript-eslint/utils": "8.64.0",
- "@typescript-eslint/visitor-keys": "8.64.0",
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/type-utils": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
@@ -893,7 +893,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.64.0",
+ "@typescript-eslint/parser": "^8.65.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
@@ -909,16 +909,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz",
- "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz",
+ "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.64.0",
- "@typescript-eslint/types": "8.64.0",
- "@typescript-eslint/typescript-estree": "8.64.0",
- "@typescript-eslint/visitor-keys": "8.64.0",
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
"debug": "^4.4.3"
},
"engines": {
@@ -934,14 +934,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz",
- "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz",
+ "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.64.0",
- "@typescript-eslint/types": "^8.64.0",
+ "@typescript-eslint/tsconfig-utils": "^8.65.0",
+ "@typescript-eslint/types": "^8.65.0",
"debug": "^4.4.3"
},
"engines": {
@@ -956,14 +956,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz",
- "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz",
+ "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.64.0",
- "@typescript-eslint/visitor-keys": "8.64.0"
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -974,9 +974,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz",
- "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz",
+ "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -991,15 +991,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz",
- "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz",
+ "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.64.0",
- "@typescript-eslint/typescript-estree": "8.64.0",
- "@typescript-eslint/utils": "8.64.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
@@ -1016,9 +1016,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz",
- "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz",
+ "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1030,16 +1030,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz",
- "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz",
+ "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.64.0",
- "@typescript-eslint/tsconfig-utils": "8.64.0",
- "@typescript-eslint/types": "8.64.0",
- "@typescript-eslint/visitor-keys": "8.64.0",
+ "@typescript-eslint/project-service": "8.65.0",
+ "@typescript-eslint/tsconfig-utils": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
@@ -1058,16 +1058,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz",
- "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz",
+ "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.64.0",
- "@typescript-eslint/types": "8.64.0",
- "@typescript-eslint/typescript-estree": "8.64.0"
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1082,13 +1082,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz",
- "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz",
+ "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.64.0",
+ "@typescript-eslint/types": "8.65.0",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -1649,9 +1649,9 @@
}
},
"node_modules/eslint": {
- "version": "10.7.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz",
- "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==",
+ "version": "10.8.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz",
+ "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==",
"dev": true,
"license": "MIT",
"workspaces": [
@@ -1661,7 +1661,7 @@
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
"@eslint/config-array": "^0.23.5",
- "@eslint/config-helpers": "^0.6.0",
+ "@eslint/config-helpers": "^0.7.0",
"@eslint/core": "^1.2.1",
"@eslint/plugin-kit": "^0.7.2",
"@humanfs/node": "^0.16.6",
@@ -1685,7 +1685,7 @@
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
- "minimatch": "^10.2.4",
+ "minimatch": "^10.2.5",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3"
},
@@ -3392,9 +3392,9 @@
}
},
"node_modules/prettier": {
- "version": "3.9.5",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz",
- "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==",
+ "version": "3.9.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz",
+ "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
"dev": true,
"license": "MIT",
"bin": {
@@ -3727,16 +3727,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz",
- "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz",
+ "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.64.0",
- "@typescript-eslint/parser": "8.64.0",
- "@typescript-eslint/typescript-estree": "8.64.0",
- "@typescript-eslint/utils": "8.64.0"
+ "@typescript-eslint/eslint-plugin": "8.65.0",
+ "@typescript-eslint/parser": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
diff --git a/package.json b/package.json
index 96aed177f..9f1943a36 100644
--- a/package.json
+++ b/package.json
@@ -49,16 +49,16 @@
"ajv": "^8.20.0",
"ajv-formats": "^3.0.1",
"cheerio": "^1.2.0",
- "eslint": "^10.7.0",
+ "eslint": "^10.8.0",
"eslint-config-prettier": "^10.1.8",
"glob": "^13.0.6",
- "prettier": "^3.9.5",
+ "prettier": "^3.9.6",
"remark-mdx": "^3.1.1",
"remark-parse": "^11.0.0",
"tsx": "^4.23.1",
"typedoc": "^0.28.20",
"typescript": "^6.0.3",
- "typescript-eslint": "^8.64.0",
+ "typescript-eslint": "^8.65.0",
"typescript-json-schema": "0.68.0",
"unified": "^11.0.5"
},
diff --git a/tools/sep-automation/package-lock.json b/tools/sep-automation/package-lock.json
index ba4ea8f72..6ce2d5132 100644
--- a/tools/sep-automation/package-lock.json
+++ b/tools/sep-automation/package-lock.json
@@ -2017,9 +2017,9 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.12",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
- "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "version": "3.3.16",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"dev": true,
"funding": [
{
@@ -2120,9 +2120,9 @@
"license": "MIT"
},
"node_modules/postcss": {
- "version": "8.5.15",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
- "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
+ "version": "8.5.23",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
+ "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
"dev": true,
"funding": [
{
@@ -2140,7 +2140,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.12",
+ "nanoid": "^3.3.16",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},