Follow Discord
Sweep 22 Oct 2026 · 21:04Z Build v2.1.309 528 read Stable v2.1.308 Latest v2.1.309 Next v2.1.309 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One capture · claude-docs

One read of Claude Documentationclaude-docs-20261020T180705Z

246 pages moved out of 277 read.

Pages moved 246 significant first
Pages read 277 in this capture
Captured 18:07 UTC
Corpus hash cf94986ff2f8 corpus-hash

What this read moved

101-125 of 246, page 5 of 10

This capture is too large to show at once. Changes 101-125 of 246 are below, significant first; the rest are on the following screens.

claude-tag/users/use-cases/review-documents Changed · +9 / -11 lines

from line 2
22 
33> Claude Tag reviews documents in a connected tool against a checklist or policy and posts findings in the thread. See single-document checks, batch reviews, filing lists, comparisons with past reviews, and a scheduled weekly sweep.
44 
5export const BetaNote = () => <Info>Claude Tag is in public beta. Features and behavior described here may change before general availability.</Info>;
5> [!NOTE]
6>
7> Claude Tag is in public beta. Features and behavior described here may change before general availability.
68 
7<BetaNote />
8 
99## How document-review prompts work
1010 
1111Each prompt below is a Slack message. You paste it in the channel where the review belongs, Claude reads the documents and the criteria from the connected tool and posts progress in that thread, and the findings land there too. The criteria can be whatever your team already uses, like a checklist, a policy document, or a filing list.
from line 78
7878 
7979## Related resources
8080 
81<CardGroup cols={2}>
82 <Card title="Find answers in your docs" href="/docs/claude-tag/users/use-cases/find-answers" horizontal arrow>
83 The same connections pointed at a single question
84 </Card>
81> **[Find answers in your docs](/docs/claude-tag/users/use-cases/find-answers)**
82>
83> The same connections pointed at a single question
8584 
86 <Card title="Set up routines" href="/docs/claude-tag/users/proactivity" horizontal arrow>
87 How the recurring review runs on a schedule
88 </Card>
89</CardGroup>
85> **[Set up routines](/docs/claude-tag/users/proactivity)**
86>
87> How the recurring review runs on a schedule
9088 

claude-tag/users/use-cases/track-projects Changed · +9 / -11 lines

from line 2
22 
33> Claude Tag posts project digests no one has to compile. See standing status updates and follow-ups on contracts, design reviews, and pull requests until they close.
44 
5export const BetaNote = () => <Info>Claude Tag is in public beta. Features and behavior described here may change before general availability.</Info>;
5> [!NOTE]
6>
7> Claude Tag is in public beta. Features and behavior described here may change before general availability.
68 
7<BetaNote />
8 
99## How project-tracking prompts work
1010 
1111This page is for anyone running a project from a Slack channel: pulling status, chasing approvals, and posting digests so the team doesn't have to ask.
from line 57
5757 
5858## Related resources
5959 
60<CardGroup cols={2}>
61 <Card title="Set up routines" href="/docs/claude-tag/users/proactivity" horizontal arrow>
62 Schedules and event triggers
63 </Card>
60> **[Set up routines](/docs/claude-tag/users/proactivity)**
61>
62> Schedules and event triggers
6463 
65 <Card title="Good habits" href="/docs/claude-tag/users/good-habits" horizontal arrow>
66 Definitions of done that close threads
67 </Card>
68</CardGroup>
64> **[Good habits](/docs/claude-tag/users/good-habits)**
65>
66> Definitions of done that close threads
6967 

claude-tag/users/use-cases/triage-requests Changed · +21 / -25 lines

from line 2
22 
33> Claude Tag triages a Slack request channel with two setup messages. See in-thread answers, duplicate flags, owner routing, weekly theme rollups, optional ticket filing, and runbooks of standing answers.
44 
5export const BetaNote = () => <Info>Claude Tag is in public beta. Features and behavior described here may change before general availability.</Info>;
5> [!NOTE]
6>
7> Claude Tag is in public beta. Features and behavior described here may change before general availability.
68 
7<BetaNote />
8 
99## How triage prompts work
1010 
1111This page is for any team with an intake channel, like #ask-it, #design-requests, or #legal-help, where people post questions and someone has to route or answer each one.
from line 26
2626 
2727Add Claude to the channel with `/invite @Claude` if it isn't there, then two messages set it up.
2828 
29<Steps>
30 <Step title="Give the channel a standing role">
31 ```text wrap theme={null}
32 @Claude remember for this channel: when someone tags you on a request, check whether it duplicates something already reported, answer it directly if the answer exists, and otherwise route it to the right owner with a one-line summary. Track recurring themes.
33 ```
29**1. Give the channel a standing role**
3430 
35 "Remember for this channel" saves the role to [channel memory](/docs/claude-tag/users/memory), so it applies to everyone's threads, not just yours. Tell requesters to include `@Claude` when they post; pin the convention or add it to the channel topic.
36 </Step>
31```text wrap theme={null}
32@Claude remember for this channel: when someone tags you on a request, check whether it duplicates something already reported, answer it directly if the answer exists, and otherwise route it to the right owner with a one-line summary. Track recurring themes.
33```
3734 
38 <Step title="Add a weekly rollup so nothing is missed">
39 ```text wrap theme={null}
40 @Claude every Friday at 3pm, post a summary of this week's requests: how many, top themes, and anything still unrouted, including posts that didn't tag you.
41 ```
35"Remember for this channel" saves the role to [channel memory](/docs/claude-tag/users/memory), so it applies to everyone's threads, not just yours. Tell requesters to include `@Claude` when they post; pin the convention or add it to the channel topic.
4236 
43 "Including posts that didn't tag you" turns the recap into a sweep, so requests that arrived without a mention are still caught. To list or cancel scheduled work later, see [Manage standing work](/docs/claude-tag/users/proactivity#manage-standing-work).
44 </Step>
45</Steps>
37**2. Add a weekly rollup so nothing is missed**
4638 
39```text wrap theme={null}
40@Claude every Friday at 3pm, post a summary of this week's requests: how many, top themes, and anything still unrouted, including posts that didn't tag you.
41```
42 
43"Including posts that didn't tag you" turns the recap into a sweep, so requests that arrived without a mention are still caught. To list or cancel scheduled work later, see [Manage standing work](/docs/claude-tag/users/proactivity#manage-standing-work).
44 
4745Answers draw on what the channel has already settled, meaning its [memory](/docs/claude-tag/users/memory) and its own past threads, and routing gets more accurate as corrections land in channel memory.
4846 
4947## Hand a request to the team that owns it
from line 74
7674 
7775## Related resources
7876 
79<CardGroup cols={2}>
80 <Card title="Set up routines" href="/docs/claude-tag/users/proactivity" horizontal arrow>
81 How the weekly rollup runs on a schedule
82 </Card>
77> **[Set up routines](/docs/claude-tag/users/proactivity)**
78>
79> How the weekly rollup runs on a schedule
8380 
84 <Card title="What Claude Tag remembers" href="/docs/claude-tag/users/memory" horizontal arrow>
85 How the standing role persists and improves
86 </Card>
87</CardGroup>
81> **[What Claude Tag remembers](/docs/claude-tag/users/memory)**
82>
83> How the standing role persists and improves
8884 

claude-tag/users/use-cases/watch-monitors Changed · +15 / -17 lines

from line 2
22 
33> Claude Tag watches dashboards and alert channels so you see one line per issue. See scheduled checks, alert investigation before anyone asks, and the prompts to set both up.
44 
5export const BetaNote = () => <Info>Claude Tag is in public beta. Features and behavior described here may change before general availability.</Info>;
5> [!NOTE]
6>
7> Claude Tag is in public beta. Features and behavior described here may change before general availability.
68 
7<BetaNote />
8 
99## How monitor-watch prompts work
1010 
1111This page is for operations and on-call teams. A monitor is an automated check in a tool like Datadog or PagerDuty that fires an alert when a metric crosses a threshold. These prompts have Claude check those dashboards or investigate alerts as they arrive.
from line 56
5656 
5757## Related resources
5858 
59<CardGroup cols={2}>
60 <Card title="Fix bugs" href="/docs/claude-tag/users/use-cases/fix-bugs" horizontal arrow>
61 When the investigation should end in a pull request
62 </Card>
59> **[Fix bugs](/docs/claude-tag/users/use-cases/fix-bugs)**
60>
61> When the investigation should end in a pull request
6362 
64 <Card title="Set up routines" href="/docs/claude-tag/users/proactivity" horizontal arrow>
65 Schedules and event triggers
66 </Card>
63> **[Set up routines](/docs/claude-tag/users/proactivity)**
64>
65> Schedules and event triggers
6766 
68 <Card title="Claude on call" href="https://claude.com/blog/ai-ci-cd-on-call" horizontal arrow>
69 How Anthropic runs Claude as first responder for CI/CD failures
70 </Card>
67> **[Claude on call](https://claude.com/blog/ai-ci-cd-on-call)**
68>
69> How Anthropic runs Claude as first responder for CI/CD failures
7170 
72 <Card title="On-call kit" href="https://github.com/anthropics/oncall-kit" horizontal arrow>
73 Reference playbooks, templates, and guided setup for an on-call channel
74 </Card>
75</CardGroup>
71> **[On-call kit](https://github.com/anthropics/oncall-kit)**
72>
73> Reference playbooks, templates, and guided setup for an on-call channel
7674 

claude-tag/users/use-cases/work-with-github Changed · +18 / -14 lines

from line 2
22 
33> Claude Tag works with your GitHub repositories from Slack: answer questions in-thread, subscribe to pull requests, and hand back chores as draft pull requests.
44 
5export const BetaNote = () => <Info>Claude Tag is in public beta. Features and behavior described here may change before general availability.</Info>;
5> [!NOTE]
6>
7> Claude Tag is in public beta. Features and behavior described here may change before general availability.
68 
7<BetaNote />
8 
99## How GitHub prompts work
1010 
1111This page is for engineers and anyone else with questions a repository can answer. Claude works with the GitHub repositories an admin granted for the channel. It reads the code to answer questions, watches pull requests you name, and hands back changes as draft pull requests.
from line 14
1414 
1515Name the repository in the first message. A session starts with no repositories checked out and clones one when the request names it. Anything Claude opens on GitHub is authored by the Claude GitHub App, so it appears in your review queue like any other pull request.
1616 
17<Note>An admin [grants a repository to the channels that need it](/docs/claude-tag/admins/attach-to-scope), and questions about the code work only in those channels. By default anyone in those channels can ask, and an admin can [restrict who can use Claude](/docs/claude-tag/admins/restrict-access#control-who-can-invoke-claude-tag).</Note>
17> [!NOTE]
18>
19> An admin [grants a repository to the channels that need it](/docs/claude-tag/admins/attach-to-scope), and questions about the code work only in those channels. By default anyone in those channels can ask, and an admin can [restrict who can use Claude](/docs/claude-tag/admins/restrict-access#control-who-can-invoke-claude-tag).
1820 
1921## Check the channel's connections
2022 
from line 98
9698@Claude watch your PR #562 in acme/data-pipeline. When CI fails, fix it and push. When a review comment arrives, address it and push. Post here after each push saying what changed and why. Done means CI is green and every comment is addressed. I do the approving and merging.
9799```
98100 
99<Warning>If you don't want Claude to merge pull requests, turn on branch protection rules that restrict who can merge. These rules apply to Claude too. Claude can't approve a pull request it opened, but the person who asked for it can; to require a second approver, see [Require a second approval on Claude's pull requests](/docs/claude-tag/admins/configure-github#require-a-second-approval-on-claude%E2%80%99s-pull-requests). Also write "I do the approving and merging" into the task, as the example does. The task wording states your intent, but it's an instruction Claude can lose track of, not a control. The branch protection rule is what enforces it.</Warning>
101> [!WARNING]
102>
103> If you don't want Claude to merge pull requests, turn on branch protection rules that restrict who can merge. These rules apply to Claude too. Claude can't approve a pull request it opened, but the person who asked for it can; to require a second approver, see [Require a second approval on Claude's pull requests](/docs/claude-tag/admins/configure-github#require-a-second-approval-on-claude%E2%80%99s-pull-requests). Also write "I do the approving and merging" into the task, as the example does. The task wording states your intent, but it's an instruction Claude can lose track of, not a control. The branch protection rule is what enforces it.
100104 
101105Ask for a post after each push so you can follow the pull request from the thread instead of opening GitHub.
102106 
from line 110
106110 
107111Each session runs in an isolated sandbox with a standard set of preinstalled tools. Two threads are two sessions with two separate sandboxes. If the repository needs a tool that the standard set doesn't include, such as a language runtime or a database client, add the install commands to `CLAUDE.md`, and Claude [runs them when its work needs them](/docs/claude-tag/admins/configure-github#install-project-dependencies).
108112 
109<Note>A `CLAUDE.md` is guidance rather than a gate. If a pull request must carry a label or pass a check, make that a repository rule. For where `CLAUDE.md` sits among channel memory, channel instructions, and skills, see [Teach Claude something that sticks](/docs/claude-tag/users/good-habits#teach-claude-something-that-sticks).</Note>
113> [!NOTE]
114>
115> A `CLAUDE.md` is guidance rather than a gate. If a pull request must carry a label or pass a check, make that a repository rule. For where `CLAUDE.md` sits among channel memory, channel instructions, and skills, see [Teach Claude something that sticks](/docs/claude-tag/users/good-habits#teach-claude-something-that-sticks).
110116 
111117## Related resources
112118 
113<CardGroup cols={2}>
114 <Card title="Fix bugs" href="/docs/claude-tag/users/use-cases/fix-bugs" horizontal arrow>
115 The full arc from a bug report to a draft pull request and green CI
116 </Card>
119> **[Fix bugs](/docs/claude-tag/users/use-cases/fix-bugs)**
120>
121> The full arc from a bug report to a draft pull request and green CI
117122 
118 <Card title="Set up routines" href="/docs/claude-tag/users/proactivity" horizontal arrow>
119 Pull request subscriptions and other standing work
120 </Card>
121</CardGroup>
123> **[Set up routines](/docs/claude-tag/users/proactivity)**
124>
125> Pull request subscriptions and other standing work
122126 

claude-tag/users/use-cases/your-own-channel Changed · +9 / -11 lines

from line 2
22 
33> Create a Slack channel for you and Claude Tag alone and run your work from it. See scratch questions, digests of channels you don't follow, a weekly status digest, follow-ups chased until they close, and a handoff that covers your time away.
44 
5export const BetaNote = () => <Info>Claude Tag is in public beta. Features and behavior described here may change before general availability.</Info>;
5> [!NOTE]
6>
7> Claude Tag is in public beta. Features and behavior described here may change before general availability.
68 
7<BetaNote />
8 
99A channel with just you and Claude in it works the same way any other channel does. This page is for anyone who wants a place for work that doesn't belong in any specific team's channel, like half-formed questions, personal digests, and the things you said you'd do.
1010 
1111## Your own channel versus a DM
from line 108
108108 
109109## Related resources
110110 
111<CardGroup cols={2}>
112 <Card title="Set up routines" href="/docs/claude-tag/users/proactivity" horizontal arrow>
113 List, edit, or disable the standing work this page sets up
114 </Card>
111> **[Set up routines](/docs/claude-tag/users/proactivity)**
112>
113> List, edit, or disable the standing work this page sets up
115114 
116 <Card title="Good habits" href="/docs/claude-tag/users/good-habits" horizontal arrow>
117 Pick the right surface and write tasks that close
118 </Card>
119</CardGroup>
115> **[Good habits](/docs/claude-tag/users/good-habits)**
116>
117> Pick the right surface and write tasks that close
120118 

claude-tag/users/when-claude-responds Changed · +6 / -6 lines

from line 2
22 
33> What Claude Tag does with a channel message nobody tagged it in, and how to turn unprompted replies off for a thread or a channel. Read this if Claude is replying too much, or went quiet.
44 
5export const BetaNote = () => <Info>Claude Tag is in public beta. Features and behavior described here may change before general availability.</Info>;
5> [!NOTE]
6>
7> Claude Tag is in public beta. Features and behavior described here may change before general availability.
68 
7<BetaNote />
8 
99Claude replies without an @-mention in one-to-one DMs, in any thread it's already part of, and to channel and group DM messages it judges warrant a reply. In a channel, Claude reads the messages and replies to some of them on its own, so an @-mention is how you guarantee a reply, not a requirement for one. This page covers how Claude decides whether to reply to a message nobody tagged it in, how to turn those replies off for a thread or a whole channel, and which messages never get a reply. Work Claude does on a schedule rather than in reply to a message is a [routine](/docs/claude-tag/users/proactivity), which has its own controls.
1010 
1111## What triggers a response
from line 57
5757 
5858Until Claude has joined a channel, you see "@-mention Claude in this channel to activate" in place of the toggle, both on the Configure page and on the channel's page in Claude Tag admin settings. You see the same line in a channel shared across workspaces that all belong to your Claude organization, because Claude runs there with your organization's default settings only. [Messages that never get a reply](#messages-that-never-get-a-reply) covers shared channels in more detail.
5959 
60<Note>
61 Separately from **Respond automatically**, an Owner or a Claude Tag admin can [turn Claude off in channels](/docs/claude-tag/admins/workspaces#turn-claude-tag-on-or-off-and-set-the-version-for-a-scope).
62</Note>
60> [!NOTE]
61>
62> Separately from **Respond automatically**, an Owner or a Claude Tag admin can [turn Claude off in channels](/docs/claude-tag/admins/workspaces#turn-claude-tag-on-or-off-and-set-the-version-for-a-scope).
6363 
6464## Messages from other apps and bots
6565 

connectors/building/after-publishing Changed · +3 / -3 lines

from line 6
66 
77This page is for the developer who owns a published listing.
88 
9<Note>
10 If your submission isn't live yet, see [Track your directory submission](/docs/directory/submission-status), which explains what each status in the portal means and who acts next.
11</Note>
9> [!NOTE]
10>
11> If your submission isn't live yet, see [Track your directory submission](/docs/directory/submission-status), which explains what each status in the portal means and who acts next.
1212 
1313You can [update what you've published](#update-a-published-connector-or-plugin), [track how a published plugin is used](#track-published-plugin-usage), [share your listing's permanent URL](#directory-listing-urls-are-permanent), or [delist](#delist-a-connector-or-plugin).
1414 

connectors/building/authentication Changed · +10 / -10 lines

from line 19
1919* Claude gives your discovery, registration, and token endpoints 10 seconds to respond and refresh requests 30 seconds, as [Endpoint latency](#endpoint-latency) describes
2020* A machine-to-machine `client_credentials` grant isn't supported, and [Anthropic-held client credentials](#anthropic-held-client-credentials) are the consent-gated alternative
2121 
22<Note>
23 - If some of your tools work without the user's account, see [Lazy authentication](/docs/connectors/building/lazy-authentication) to let people use those right away and sign in only when Claude reaches a tool that needs their account
24 - If you want enterprise users to connect through their organization's SSO without a consent screen, see [Enterprise Managed Auth](/docs/connectors/building/enterprise-managed-auth)
25</Note>
22> [!NOTE]
23>
24> * If some of your tools work without the user's account, see [Lazy authentication](/docs/connectors/building/lazy-authentication) to let people use those right away and sign in only when Claude reaches a tool that needs their account
25> * If you want enterprise users to connect through their organization's SSO without a consent screen, see [Enterprise Managed Auth](/docs/connectors/building/enterprise-managed-auth)
2626 
2727Use this page to [pick an authentication type](#supported-authentication-types), [register Claude as an OAuth client](#register-claude-as-an-oauth-client), [make discovery and redirects work](#oauth-discovery-and-redirect-uris), and [meet the token endpoint requirements](#token-endpoint-requirements).
2828 
from line 153
153153 
154154If your `401` doesn't include a `resource_metadata` pointer, Claude can still infer the metadata location by probing your MCP server's origin: `/.well-known/oauth-protected-resource/<your-mcp-path>` first, then `/.well-known/oauth-protected-resource`. Treat this as a fallback, because it works only when your platform serves `/.well-known/*` paths.
155155 
156<Note>
157 If your authorization server is Microsoft Entra ID, you must also register the MCP server URL as an Application ID URI on your Entra app registration, or the token request fails with `AADSTS9010010`. By default, Entra accepts that URL as an Application ID URI only when it's on a domain your tenant has verified, as described in [Microsoft's identifier URI restrictions](https://learn.microsoft.com/en-us/entra/identity-platform/identifier-uri-restrictions), so an MCP server on a platform hostname such as `*.azurewebsites.net` needs a custom domain first. See [the troubleshooting entry](/docs/connectors/building/troubleshooting#microsoft-entra-id-rejects-the-resource-value) for the fix.
158</Note>
156> [!NOTE]
157>
158> If your authorization server is Microsoft Entra ID, you must also register the MCP server URL as an Application ID URI on your Entra app registration, or the token request fails with `AADSTS9010010`. By default, Entra accepts that URL as an Application ID URI only when it's on a domain your tenant has verified, as described in [Microsoft's identifier URI restrictions](https://learn.microsoft.com/en-us/entra/identity-platform/identifier-uri-restrictions), so an MCP server on a platform hostname such as `*.azurewebsites.net` needs a custom domain first. See [the troubleshooting entry](/docs/connectors/building/troubleshooting#microsoft-entra-id-rejects-the-resource-value) for the fix.
159159 
160160If you control both hosts, an alternative is to serve the MCP endpoint and the authorization server behind a single custom domain that can route both `/.well-known/*` and your MCP path.
161161 
162<Tip>
163 A common symptom of a discovery failure is that your MCP server receives the initial request but your authorization server sees no traffic at all. That happens when neither path works: there's no `WWW-Authenticate: Bearer resource_metadata=…` header on your `401`, and the well-known paths on your MCP server's origin return `404`. With no metadata to read, Claude never learns where your authorization server is, and the connection fails with "Couldn't reach the MCP server." See [troubleshooting](/docs/connectors/building/troubleshooting) for the full diagnostic flow.
164</Tip>
162> [!TIP]
163>
164> A common symptom of a discovery failure is that your MCP server receives the initial request but your authorization server sees no traffic at all. That happens when neither path works: there's no `WWW-Authenticate: Bearer resource_metadata=…` header on your `401`, and the well-known paths on your MCP server's origin return `404`. With no metadata to read, Claude never learns where your authorization server is, and the connection fails with "Couldn't reach the MCP server." See [troubleshooting](/docs/connectors/building/troubleshooting) for the full diagnostic flow.
165165 
166166### Callback URLs
167167 

connectors/building/directory-vs-custom Changed · +6 / -6 lines

from line 6
66 
77This page is for developers deciding whether to list a server in the directory, distribute it as a custom connector, or do both. It compares the two, shows the install link each one gets, and covers listing patterns for enterprise and multi-tenant servers.
88 
9<Note>
10 If you want to know what directory and custom connectors look like to a Claude user, including the Verified and Community labels, see [connector verification](/docs/connectors/verification).
11</Note>
9> [!NOTE]
10>
11> If you want to know what directory and custom connectors look like to a Claude user, including the Verified and Community labels, see [connector verification](/docs/connectors/verification).
1212 
1313## Compare directory and custom connectors
1414 
from line 62
6262 
6363When a user follows the link, claude.ai opens the dialog with the name and URL prefilled and shows a notice that the values came from an external link. The user reviews the values and confirms before anything is added. A signed-out user is prompted to sign in first and then sees the prefilled dialog.
6464 
65<Note>
66 Install links only prefill the form. They don't bypass review by the user, and they don't grant your server any permissions the user hasn't confirmed.
67</Note>
65> [!NOTE]
66>
67> Install links only prefill the form. They don't bypass review by the user, and they don't grant your server any permissions the user hasn't confirmed.
6868 
6969Organization Owners can use the same parameters on the admin path to prefill the org-wide connector dialog:
7070 

connectors/building/enterprise-managed-auth Changed · +47 / -51 lines

from line 2
22 
33> Accept identity assertions from enterprise SSO so users connect to your MCP server without a separate OAuth consent step.
44 
5<Note>
6 Enterprise Managed Auth is available on Claude Team and Enterprise plans. After your authorization server supports it, email `[email protected]` to have [your directory listing](/docs/connectors/building/managing-your-listing) marked as supporting Enterprise Managed Auth.
7</Note>
5> [!NOTE]
6>
7> Enterprise Managed Auth is available on Claude Team and Enterprise plans. After your authorization server supports it, email `[email protected]` to have [your directory listing](/docs/connectors/building/managing-your-listing) marked as supporting Enterprise Managed Auth.
88 
99Enterprise Managed Auth (EMA) lets a user connect to your MCP server silently, using the single sign-on session they already have with their organization. Instead of showing each user an OAuth consent screen, Claude presents your authorization server with an identity assertion: a signed JSON Web Token (JWT), issued by the customer's identity provider, that vouches for the user's identity.
1010 
from line 16
1616 
1717When a user whose organization has Enterprise Managed Auth configured invokes your connector, Claude obtains a signed identity assertion for that user and exchanges it directly at your token endpoint for an access token. The user never sees a browser redirect or a consent screen, and your MCP server receives the same kind of bearer token it would after the interactive OAuth flow. The diagram shows the exchange between Claude, your authorization server, and your MCP server.
1818 
19<img className="block dark:hidden" src="https://mintcdn.com/claude-ai/-njlLvrWxFCRdJVz/images/connectors/enterprise-managed-auth-exchange.svg?fit=max&auto=format&n=-njlLvrWxFCRdJVz&q=85&s=2bdd0b680bda337b37632516b8dc5e84" alt="Sequence diagram with three participants: Claude, your authorization server, and your MCP server. The user is already signed in to Claude through their organization's single sign-on. 1, Claude sends POST /token to your authorization server with the jwt-bearer grant and the signed JWT assertion. 2, your authorization server fetches the issuer's JWKS and verifies the signature. 3, it validates iss, aud, exp, sub, and client_id. 4, it returns an access_token to Claude. 5, Claude sends the tool call to your MCP server with the Bearer access_token. 6, your MCP server returns the tool result to Claude. Requests are solid arrows and responses are dashed arrows." width="1000" height="560" data-path="images/connectors/enterprise-managed-auth-exchange.svg" />
19![Sequence diagram with three participants: Claude, your authorization server, and your MCP server. The user is already signed in to Claude through their organization's single sign-on. 1, Claude sends POST /token to your authorization server with the jwt-bearer grant and the signed JWT assertion. 2, your authorization server fetches the issuer's JWKS and verifies the signature. 3, it validates iss, aud, exp, sub, and client\_id. 4, it returns an access\_token to Claude. 5, Claude sends the tool call to your MCP server with the Bearer access\_token. 6, your MCP server returns the tool result to Claude. Requests are solid arrows and responses are dashed arrows.](https://mintcdn.com/claude-ai/-njlLvrWxFCRdJVz/images/connectors/enterprise-managed-auth-exchange.svg?fit=max\&auto=format\&n=-njlLvrWxFCRdJVz\&q=85\&s=2bdd0b680bda337b37632516b8dc5e84)
2020 
21<img className="hidden dark:block" src="https://mintcdn.com/claude-ai/-njlLvrWxFCRdJVz/images/connectors/enterprise-managed-auth-exchange-dark.svg?fit=max&auto=format&n=-njlLvrWxFCRdJVz&q=85&s=5b8c6c81ef448cfb08deb05f0d02b712" alt="Sequence diagram with three participants: Claude, your authorization server, and your MCP server. The user is already signed in to Claude through their organization's single sign-on. 1, Claude sends POST /token to your authorization server with the jwt-bearer grant and the signed JWT assertion. 2, your authorization server fetches the issuer's JWKS and verifies the signature. 3, it validates iss, aud, exp, sub, and client_id. 4, it returns an access_token to Claude. 5, Claude sends the tool call to your MCP server with the Bearer access_token. 6, your MCP server returns the tool result to Claude. Requests are solid arrows and responses are dashed arrows." width="1000" height="560" data-path="images/connectors/enterprise-managed-auth-exchange-dark.svg" />
22 
2321Two parties are involved in this exchange: the customer's identity provider signs the assertion, and your authorization server verifies it and issues the access token. Both roles are often served by commercial identity platforms, so the distinction here is about which tenant plays which role rather than about product type. The identity provider publishes its signing keys as a JSON Web Key Set, and your authorization server fetches that key set to verify each assertion.
2422 
2523The Enterprise Managed Auth flow is defined by the [MCP enterprise managed authorization extension](https://modelcontextprotocol.io/extensions/auth/enterprise-managed-authorization) and is built on the standard [JWT bearer authorization grant (RFC 7523)](https://datatracker.ietf.org/doc/html/rfc7523). The assertion profile follows the [Identity Assertion JWT Authorization Grant](https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/).
from line 37
3937* Your MCP server implements [MCP authorization](https://modelcontextprotocol.io/specification/draft/basic/authorization), including Protected Resource Metadata (PRM) discovery, and follows the [MCP security best practices](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices). See the [authentication guide](/docs/connectors/building/authentication) for Claude-specific requirements
4038* Your authorization server registers Claude using either [Anthropic-held client credentials](/docs/connectors/building/authentication#anthropic-held-client-credentials) or a [Client ID Metadata Document](/docs/connectors/building/authentication#dcr-and-cimd-details)
4139 
42<Warning>
43 Dynamic Client Registration (DCR) isn't supported with Enterprise Managed Auth. The identity provider stamps a fixed `client_id` into every assertion it issues, so your authorization server must already recognize that client before the first assertion arrives. A client created on the fly through DCR can't satisfy this requirement because its identifier never matches the value in the assertion.
44</Warning>
40> [!WARNING]
41>
42> Dynamic Client Registration (DCR) isn't supported with Enterprise Managed Auth. The identity provider stamps a fixed `client_id` into every assertion it issues, so your authorization server must already recognize that client before the first assertion arrives. A client created on the fly through DCR can't satisfy this requirement because its identifier never matches the value in the assertion.
4543 
4644## Authorization server requirements
4745 
from line 47
4947 
5048If your MCP server relies on a hosted identity platform, there is typically no code to write. Confirm that the platform supports the JWT bearer authorization grant and enable it for your tenant. Support varies by product, and some commercial authorization servers and identity platforms don't support it yet, so confirm that yours does and that the customer's identity provider can be registered as a trusted issuer. If you run your own authorization server, the steps in this section describe what it needs to support.
5149 
52<Steps>
53 <Step title="Ensure the JWT bearer grant is supported">
54 Your authorization server must accept `urn:ietf:params:oauth:grant-type:jwt-bearer` at its token endpoint and advertise it in the `grant_types_supported` array of its [authorization server metadata (RFC 8414)](https://datatracker.ietf.org/doc/html/rfc8414). In this example metadata, the highlighted entry is the one Claude looks for:
50**1. Ensure the JWT bearer grant is supported**
5551 
56 ```json {7} theme={null}
57 {
58 "issuer": "https://auth.example.com",
59 "token_endpoint": "https://auth.example.com/token",
60 "grant_types_supported": [
61 "authorization_code",
62 "refresh_token",
63 "urn:ietf:params:oauth:grant-type:jwt-bearer"
64 ]
65 }
66 ```
52Your authorization server must accept `urn:ietf:params:oauth:grant-type:jwt-bearer` at its token endpoint and advertise it in the `grant_types_supported` array of its [authorization server metadata (RFC 8414)](https://datatracker.ietf.org/doc/html/rfc8414). In this example metadata, the highlighted entry is the one Claude looks for:
6753 
68 Claude reads this metadata to discover whether your server supports Enterprise Managed Auth. The grant type must be listed here for Claude to offer the feature to the customer, even if your token endpoint would already accept it silently.
69 </Step>
54```json {7} theme={null}
55{
56 "issuer": "https://auth.example.com",
57 "token_endpoint": "https://auth.example.com/token",
58 "grant_types_supported": [
59 "authorization_code",
60 "refresh_token",
61 "urn:ietf:params:oauth:grant-type:jwt-bearer"
62 ]
63}
64```
7065 
71 <Step title="Register the trusted issuer">
72 For each customer, your authorization server needs to trust that customer's identity provider as a JWT issuer. Your authorization server fetches the identity provider's JSON Web Key Set and uses it to verify the signature on every incoming assertion.
66Claude reads this metadata to discover whether your server supports Enterprise Managed Auth. The grant type must be listed here for Claude to offer the feature to the customer, even if your token endpoint would already accept it silently.
7367 
74 Your authorization server is responsible for maintaining an explicit allowlist of trusted issuer URLs per tenant rather than accepting any well-formed JWT. It must reject an assertion whose `iss` isn't on the tenant's allowlist with `invalid_grant`, even if the signature is valid.
68**2. Register the trusted issuer**
7569 
76 <Warning>
77 Never accept an identity assertion without full validation. Your authorization server must verify the signature, issuer, audience, expiry, and subject on every request. Use the JWT validation built into your authorization server product. If you need to inspect assertions in your own code, use the validation library or token introspection endpoint provided by your authorization server vendor rather than writing custom verification logic.
78 </Warning>
79 </Step>
70For each customer, your authorization server needs to trust that customer's identity provider as a JWT issuer. Your authorization server fetches the identity provider's JSON Web Key Set and uses it to verify the signature on every incoming assertion.
8071 
81 <Step title="Understand the token request">
82 Claude sends a form-encoded `POST` to your authorization server's token endpoint. The highlighted parameters are the JWT bearer grant type, the signed assertion, and the `client_id` your server must already recognize:
72Your authorization server is responsible for maintaining an explicit allowlist of trusted issuer URLs per tenant rather than accepting any well-formed JWT. It must reject an assertion whose `iss` isn't on the tenant's allowlist with `invalid_grant`, even if the signature is valid.
8373 
84 ```http {5-7} theme={null}
85 POST /token HTTP/1.1
86 Host: auth.example.com
87 Content-Type: application/x-www-form-urlencoded
74> [!WARNING]
75>
76> Never accept an identity assertion without full validation. Your authorization server must verify the signature, issuer, audience, expiry, and subject on every request. Use the JWT validation built into your authorization server product. If you need to inspect assertions in your own code, use the validation library or token introspection endpoint provided by your authorization server vendor rather than writing custom verification logic.
8877 
89 grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
90 &assertion=eyJhbGciOi...
91 &client_id=your-registered-client-id
92 &scope=openid profile
93 &resource=https://mcp.example.com
94 ```
78**3. Understand the token request**
9579 
96 The `assertion` parameter carries the signed JWT. The `client_id` is the value Claude is registered under at your authorization server. Claude also includes the `resource` parameter ([Resource Indicators, RFC 8707](https://www.rfc-editor.org/rfc/rfc8707)) set to your MCP server URL whenever the customer's identity provider supports forwarding it. Some identity provider configurations cannot pass a resource indicator through, so your authorization server should accept the request whether or not `resource` is present and use it for audience binding when it is.
80Claude sends a form-encoded `POST` to your authorization server's token endpoint. The highlighted parameters are the JWT bearer grant type, the signed assertion, and the `client_id` your server must already recognize:
9781 
98 Your authorization server validates the assertion according to the [JWT bearer token processing rules (RFC 7523 section 3)](https://datatracker.ietf.org/doc/html/rfc7523#section-3) and returns a standard OAuth token response. Claude then presents the returned access token as a `Bearer` credential on calls to your MCP server, exactly as it does after the interactive flow.
82```http {5-7} theme={null}
83POST /token HTTP/1.1
84Host: auth.example.com
85Content-Type: application/x-www-form-urlencoded
9986 
100 <Note>
101 Your authorization server sets the access token lifetime, and the customer's identity provider sets the assertion lifetime. Anthropic doesn't control either value.
102 </Note>
103 </Step>
104</Steps>
87grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
88&assertion=eyJhbGciOi...
89&client_id=your-registered-client-id
90&scope=openid profile
91&resource=https://mcp.example.com
92```
93 
94The `assertion` parameter carries the signed JWT. The `client_id` is the value Claude is registered under at your authorization server. Claude also includes the `resource` parameter ([Resource Indicators, RFC 8707](https://www.rfc-editor.org/rfc/rfc8707)) set to your MCP server URL whenever the customer's identity provider supports forwarding it. Some identity provider configurations cannot pass a resource indicator through, so your authorization server should accept the request whether or not `resource` is present and use it for audience binding when it is.
95 
96Your authorization server validates the assertion according to the [JWT bearer token processing rules (RFC 7523 section 3)](https://datatracker.ietf.org/doc/html/rfc7523#section-3) and returns a standard OAuth token response. Claude then presents the returned access token as a `Bearer` credential on calls to your MCP server, exactly as it does after the interactive flow.
97 
98> [!NOTE]
99>
100> Your authorization server sets the access token lifetime, and the customer's identity provider sets the assertion lifetime. Anthropic doesn't control either value.
105101 
106102### Access token lifetime
107103 

connectors/building/index Changed · +8 / -8 lines

from line 6
66 
77This page is for developers building a remote MCP server for other people to use in Claude. It covers the decisions you make as you build, in the order you meet them, and what Claude's MCP client supports for each one.
88 
9<Note>
10 * If you're new to the protocol itself, see [Model Context Protocol (MCP)](/docs/connectors/building/mcp) for a short orientation
11 * If you want to build a minimal server first and watch Claude call it, see [Build your first MCP server for Claude](/docs/connectors/building/quickstart)
12 * If you're not sure your plugin needs an MCP server, see [Decide what to include in your plugin](/docs/connectors/building/what-to-build)
13</Note>
9> [!NOTE]
10>
11> * If you're new to the protocol itself, see [Model Context Protocol (MCP)](/docs/connectors/building/mcp) for a short orientation
12> * If you want to build a minimal server first and watch Claude call it, see [Build your first MCP server for Claude](/docs/connectors/building/quickstart)
13> * If you're not sure your plugin needs an MCP server, see [Decide what to include in your plugin](/docs/connectors/building/what-to-build)
1414 
1515## Plan your server
1616 
1717Claude implements a subset of the MCP specification, with its own callback URL and its own size and timeout limits.
1818 
19<Tip>
20 To build with Claude's help, install the official [`mcp-server-dev` plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev) in Claude Code. It walks you through building, testing, and packaging an MCP server interactively, using these docs as its reference.
21</Tip>
19> [!TIP]
20>
21> To build with Claude's help, install the official [`mcp-server-dev` plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev) in Claude Code. It walks you through building, testing, and packaging an MCP server interactively, using these docs as its reference.
2222 
2323### Choose where the server runs
2424 

connectors/building/lazy-authentication Changed · +73 / -75 lines

from line 17
17173. They click **Connect** and sign in to your service in a popup.
18184. Claude retries the same tool call automatically with the new token, and the turn continues with no context lost.
1919 
20<Note>
21 If the user's organization has [Enterprise Managed Auth](/docs/connectors/building/enterprise-managed-auth) configured for your connector, the same refusal triggers a silent token exchange instead of the **Connect** card. Claude retries the tool call automatically and the user sees no prompt.
22</Note>
20> [!NOTE]
21>
22> If the user's organization has [Enterprise Managed Auth](/docs/connectors/building/enterprise-managed-auth) configured for your connector, the same refusal triggers a silent token exchange instead of the **Connect** card. Claude retries the tool call automatically and the user sees no prompt.
2323 
2424## Build lazy authentication on your server
2525 
from line 136
136136{"jsonrpc":"2.0","result":{"isError":true,"content":[{"type":"text","text":"Please sign in"}]},"id":1}
137137```
138138 
139<Warning>
140 A `200` with `isError: true` is an application-level tool failure. Claude passes the error text to the model as the tool result and moves on, and there is no auth prompt. Only a transport-level `401` causes Claude to pause the call, run the OAuth flow, and retry. A `403` triggers re-authentication only when accompanied by `WWW-Authenticate: Bearer error="insufficient_scope"` for [scope step-up](#ask-for-more-scope-with-403), and any other `403` is surfaced as a terminal error. If users are seeing "please sign in" text in the chat instead of a **Connect** button, the server is returning the wrong one.
141</Warning>
139> [!WARNING]
140>
141> A `200` with `isError: true` is an application-level tool failure. Claude passes the error text to the model as the tool result and moves on, and there is no auth prompt. Only a transport-level `401` causes Claude to pause the call, run the OAuth flow, and retry. A `403` triggers re-authentication only when accompanied by `WWW-Authenticate: Bearer error="insufficient_scope"` for [scope step-up](#ask-for-more-scope-with-403), and any other `403` is surfaced as a terminal error. If users are seeing "please sign in" text in the chat instead of a **Connect** button, the server is returning the wrong one.
142142 
143143### Serve the discovery documents
144144 
from line 203
203203}
204204```
205205 
206<Note>
207 Claude selects CIMD only when the authorization-server metadata advertises both `client_id_metadata_document_supported: true` and `"none"` in `token_endpoint_auth_methods_supported`. The second is required because Claude's CIMD client authenticates as a public client with `token_endpoint_auth_method: "none"`, so the token endpoint must accept [PKCE](https://datatracker.ietf.org/doc/html/rfc7636)-only requests without a client secret. If either property is missing, Claude falls back to looking for a `registration_endpoint`.
208</Note>
206> [!NOTE]
207>
208> Claude selects CIMD only when the authorization-server metadata advertises both `client_id_metadata_document_supported: true` and `"none"` in `token_endpoint_auth_methods_supported`. The second is required because Claude's CIMD client authenticates as a public client with `token_endpoint_auth_method: "none"`, so the token endpoint must accept [PKCE](https://datatracker.ietf.org/doc/html/rfc7636)-only requests without a client secret. If either property is missing, Claude falls back to looking for a `registration_endpoint`.
209209 
210210If you move to a real issuer, keep `client_id_metadata_document_supported: true` in that issuer's metadata if you want registration-free onboarding for Claude clients.
211211 
from line 246
246246 
247247You can confirm the public and protected paths with `curl` and Claude Code against your own server before connecting it to Claude as a custom connector, then check the sign-in prompt in a conversation. The commands below use `http://localhost:3000/mcp` as the server URL and the example's tool names; substitute your own.
248248 
249<Steps>
250 <Step title="Call a public tool without a token">
251 In a terminal, send a `tools/call` for a public tool with no `Authorization` header, with `-i` so the status line prints:
249**1. Call a public tool without a token**
252250 
253 ```bash theme={null}
254 curl -si http://localhost:3000/mcp \
255 -H 'Content-Type: application/json' \
256 -H 'Accept: application/json, text/event-stream' \
257 -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_products","arguments":{}}}'
258 ```
251In a terminal, send a `tools/call` for a public tool with no `Authorization` header, with `-i` so the status line prints:
259252 
260 The server answers `200 OK` with the JSON-RPC tool result and no `WWW-Authenticate` header.
261 </Step>
253```bash theme={null}
254curl -si http://localhost:3000/mcp \
255 -H 'Content-Type: application/json' \
256 -H 'Accept: application/json, text/event-stream' \
257 -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_products","arguments":{}}}'
258```
262259 
263 <Step title="Call a protected tool without a token">
264 Send the same request for a protected tool:
260The server answers `200 OK` with the JSON-RPC tool result and no `WWW-Authenticate` header.
265261 
266 ```bash theme={null}
267 curl -si http://localhost:3000/mcp \
268 -H 'Content-Type: application/json' \
269 -H 'Accept: application/json, text/event-stream' \
270 -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_my_orders","arguments":{}}}'
271 ```
262**2. Call a protected tool without a token**
272263 
273 The server answers `401 Unauthorized` with a `WWW-Authenticate` header carrying `resource_metadata`. If you see `200` with `isError: true` instead, your server is returning a tool error rather than failing the HTTP request.
274 </Step>
264Send the same request for a protected tool:
275265 
276 <Step title="Check both paths from Claude Code">
277 If you have [Claude Code](https://code.claude.com/docs/en/setup) installed and signed in, you can see how a Claude client treats each path before you set up a tunnel, because Claude Code connects to a `localhost` server directly. In your terminal, from your server's project folder, add the server:
266```bash theme={null}
267curl -si http://localhost:3000/mcp \
268 -H 'Content-Type: application/json' \
269 -H 'Accept: application/json, text/event-stream' \
270 -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_my_orders","arguments":{}}}'
271```
278272 
279 ```bash theme={null}
280 claude mcp add --transport http authtest http://localhost:3000/mcp
281 ```
273The server answers `401 Unauthorized` with a `WWW-Authenticate` header carrying `resource_metadata`. If you see `200` with `isError: true` instead, your server is returning a tool error rather than failing the HTTP request.
282274 
283 Then check the connection:
275**3. Check both paths from Claude Code**
284276 
285 ```bash theme={null}
286 claude mcp list
287 ```
277If you have [Claude Code](https://code.claude.com/docs/en/setup) installed and signed in, you can see how a Claude client treats each path before you set up a tunnel, because Claude Code connects to a `localhost` server directly. In your terminal, from your server's project folder, add the server:
288278 
289 The list shows `authtest: http://localhost:3000/mcp (HTTP) - ✔ Connected` with no sign-in, because connecting and listing tools never reach the gate. Next, have Claude call the public tool with [`claude -p`](https://code.claude.com/docs/en/headless), which sends one prompt and prints the answer:
279```bash theme={null}
280claude mcp add --transport http authtest http://localhost:3000/mcp
281```
290282 
291 ```bash theme={null}
292 claude -p "List the products from the authtest server." --allowedTools mcp__authtest__list_products
293 ```
283Then check the connection:
294284 
295 Claude reports the products and no sign-in prompt appears. Run the same command asking for your orders with `--allowedTools mcp__authtest__get_my_orders`. The tool result Claude receives is `MCP server "authtest" needs you to sign in again (run /mcp to re-authenticate)` rather than order data.
285```bash theme={null}
286claude mcp list
287```
296288 
297 To sign in from Claude Code, run `/mcp` in an interactive session as [Authenticate with remote MCP servers](https://code.claude.com/docs/en/mcp#authenticate-with-remote-mcp-servers) describes. When you're done testing, run `claude mcp remove authtest`.
298 </Step>
289The list shows `authtest: http://localhost:3000/mcp (HTTP) - ✔ Connected` with no sign-in, because connecting and listing tools never reach the gate. Next, have Claude call the public tool with [`claude -p`](https://code.claude.com/docs/en/headless), which sends one prompt and prints the answer:
299290 
300 <Step title="Add the server to Claude as a custom connector">
301 Claude reaches custom connectors from Anthropic's infrastructure, so `localhost` isn't reachable directly. To add a local server:
291```bash theme={null}
292claude -p "List the products from the authtest server." --allowedTools mcp__authtest__list_products
293```
302294 
303 1. Expose the server over a public HTTPS tunnel, such as `cloudflared tunnel --url http://localhost:3000` or `ngrok http 3000`. Keep the tunnel up only while you test, because it exposes your local server publicly and the example's `/authorize`, `/token`, and `isTokenValid()` are stubs that treat anyone as signed in.
304 2. In Claude, go to [**Customize > Connectors**](https://claude.ai/customize/connectors) and select **Add custom connector**.
305 3. Enter the tunnel's `/mcp` URL.
295Claude reports the products and no sign-in prompt appears. Run the same command asking for your orders with `--allowedTools mcp__authtest__get_my_orders`. The tool result Claude receives is `MCP server "authtest" needs you to sign in again (run /mcp to re-authenticate)` rather than order data.
306296 
307 See [Test a local server](/docs/connectors/building/testing#test-a-local-server) for details. If your app comes from the SDK's `createMcpExpressApp()`, that section also shows the `allowedHosts` option you need before requests through the tunnel succeed.
308 </Step>
297To sign in from Claude Code, run `/mcp` in an interactive session as [Authenticate with remote MCP servers](https://code.claude.com/docs/en/mcp#authenticate-with-remote-mcp-servers) describes. When you're done testing, run `claude mcp remove authtest`.
309298 
310 <Step title="Try both paths in a conversation">
311 1. Ask Claude to list products. No sign-in prompt appears.
312 2. Ask for your orders. The inline **Connect** card appears, and after you authenticate the same call completes.
313 </Step>
314</Steps>
299**4. Add the server to Claude as a custom connector**
300 
301Claude reaches custom connectors from Anthropic's infrastructure, so `localhost` isn't reachable directly. To add a local server:
302 
3031. Expose the server over a public HTTPS tunnel, such as `cloudflared tunnel --url http://localhost:3000` or `ngrok http 3000`. Keep the tunnel up only while you test, because it exposes your local server publicly and the example's `/authorize`, `/token`, and `isTokenValid()` are stubs that treat anyone as signed in.
3042. In Claude, go to [**Customize > Connectors**](https://claude.ai/customize/connectors) and select **Add custom connector**.
3053. Enter the tunnel's `/mcp` URL.
306 
307See [Test a local server](/docs/connectors/building/testing#test-a-local-server) for details. If your app comes from the SDK's `createMcpExpressApp()`, that section also shows the `allowedHosts` option you need before requests through the tunnel succeed.
308 
309**5. Try both paths in a conversation**
310 
3111. Ask Claude to list products. No sign-in prompt appears.
3122. Ask for your orders. The inline **Connect** card appears, and after you authenticate the same call completes.
315313 
316314## Next steps
317315 

connectors/building/managing-your-listing Changed · +6 / -6 lines

from line 4
44 
55Organizations that submit a connector to the [directory](/docs/connectors/directory) get a submissions dashboard in the developer portal at [claude.ai/directory/manage](https://claude.ai/directory/manage). This page is for connector authors who have already [submitted a connector](/docs/connectors/building/submission). Use the dashboard to [track a submission through review](#track-submission-status), [monitor your published server's health and usage](#server-health-and-usage-metrics), and [edit your listing](#edit-your-listing).
66 
7<Note>
8 This page covers directory-listed remote MCP servers. The dashboard shows only your own organization's submissions. Custom connectors and local servers packaged as desktop extensions don't appear here.
9</Note>
7> [!NOTE]
8>
9> This page covers directory-listed remote MCP servers. The dashboard shows only your own organization's submissions. Custom connectors and local servers packaged as desktop extensions don't appear here.
1010 
1111## Access the dashboard
1212 
from line 22
2222 
2323Once your server is published, its **Overview** and **Usage** tabs in the developer portal show a health badge, topline usage numbers, an error breakdown, and usage by product and by tool.
2424 
25<Note>
26 Metrics are in beta. They're computed daily from directory usage and can lag by up to 24 hours. Time windows with fewer than 5 calls, per-tool rows with fewer than 5 calls, and per-product rows with fewer than 50 calls are omitted.
27</Note>
25> [!NOTE]
26>
27> Metrics are in beta. They're computed daily from directory usage and can lag by up to 24 hours. Time windows with fewer than 5 calls, per-tool rows with fewer than 5 calls, and per-product rows with fewer than 50 calls are omitted.
2828 
2929### Review what the metrics cover
3030 

connectors/building/mcp-apps/design-guidelines Changed · +31 / -31 lines

from line 4
44 
55MCP Apps are interactive interfaces that appear within Claude's conversational flow. These guidelines are for developers designing an MCP App's UI.
66 
7<Note>
8 If you haven't built and connected an MCP App yet, see [Get started with MCP Apps](/docs/connectors/building/mcp-apps/getting-started).
9</Note>
7> [!NOTE]
8>
9> If you haven't built and connected an MCP App yet, see [Get started with MCP Apps](/docs/connectors/building/mcp-apps/getting-started).
1010 
1111Use the guidelines to [pick a display mode](#display-modes), [adapt to mobile](#mobile-guidelines), match Claude's [visual design](#visual-design) with the host's [style variables](#style-variables), and decide which [interactions](#interaction-patterns) belong in your app and which belong in chat.
1212 
13<Tip>
14 The [Figma UI kit](https://www.figma.com/community/file/1597641111449594397/mcp-apps-for-claude) has components and patterns to start from.
15</Tip>
13> [!TIP]
14>
15> The [Figma UI kit](https://www.figma.com/community/file/1597641111449594397/mcp-apps-for-claude) has components and patterns to start from.
1616 
1717## Design for the conversation
1818 
from line 55
5555* Brief summaries with optional expansion
5656* Quick actions that continue the conversation
5757 
58<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/inline-card-1.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=0c7825c89be73ad56d0a90e4c979401d" alt="Inline card example showing a compact component" width="1999" height="1423" data-path="images/mcp-apps/inline-card-1.png" />
58![Inline card example showing a compact component](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/inline-card-1.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=0c7825c89be73ad56d0a90e4c979401d)
5959 
60<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/inline-card-2.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=344f114b1012838b4805b48d496201c0" alt="Inline card example showing a data display" width="1999" height="1423" data-path="images/mcp-apps/inline-card-2.png" />
60![Inline card example showing a data display](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/inline-card-2.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=344f114b1012838b4805b48d496201c0)
6161 
6262Inline cards have these constraints:
6363 
from line 69
6969 
7070On mobile, inline cards render full-width within the conversation. Make every tap target at least 44pt, and adapt content to narrower viewports without horizontal scrolling.
7171 
72<img src="https://mintcdn.com/claude-ai/sLZLADaApRAVEV6C/images/mcp-apps/inline-card-mobile.png?fit=max&auto=format&n=sLZLADaApRAVEV6C&q=85&s=3d2afa48da4f1d31cce9c0bb91a18807" alt="Inline card mobile examples" width="1999" height="838" data-path="images/mcp-apps/inline-card-mobile.png" />
72![Inline card mobile examples](https://mintcdn.com/claude-ai/sLZLADaApRAVEV6C/images/mcp-apps/inline-card-mobile.png?fit=max\&auto=format\&n=sLZLADaApRAVEV6C\&q=85\&s=3d2afa48da4f1d31cce9c0bb91a18807)
7373 
7474### Inline carousel
7575 
from line 80
8080* Media galleries
8181* Any set of comparable items
8282 
83<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/inline-carousel-1.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=ef6b5615726ad25708ebdedf586226ae" alt="Inline carousel example showing browsable items" width="1999" height="1423" data-path="images/mcp-apps/inline-carousel-1.png" />
83![Inline carousel example showing browsable items](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/inline-carousel-1.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=ef6b5615726ad25708ebdedf586226ae)
8484 
8585Inline carousels have these constraints:
8686 
from line 92
9292 
9393On mobile, carousel cards are optimized for horizontal swipe. Design for thumb reach by keeping primary actions in the lower portion of cards, and let the next card peek into view to signal that the row scrolls.
9494 
95<img src="https://mintcdn.com/claude-ai/sLZLADaApRAVEV6C/images/mcp-apps/inline-carousel-mobile.png?fit=max&auto=format&n=sLZLADaApRAVEV6C&q=85&s=2bf64edd4fdefe8a37188ca4f2f32f4e" alt="Inline carousel mobile examples" width="1999" height="1022" data-path="images/mcp-apps/inline-carousel-mobile.png" />
95![Inline carousel mobile examples](https://mintcdn.com/claude-ai/sLZLADaApRAVEV6C/images/mcp-apps/inline-carousel-mobile.png?fit=max\&auto=format\&n=sLZLADaApRAVEV6C\&q=85\&s=2bf64edd4fdefe8a37188ca4f2f32f4e)
9696 
9797### Full screen
9898 
from line 104
104104* Content that benefits from focused attention
105105* Rich tasks requiring more space than inline allows
106106 
107<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/fullscreen-1.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=a4b491bb475bab8ed146c1bc953516ec" alt="Full screen mode example" width="1999" height="1423" data-path="images/mcp-apps/fullscreen-1.png" />
107![Full screen mode example](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/fullscreen-1.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=a4b491bb475bab8ed146c1bc953516ec)
108108 
109<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/fullscreen-2.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=8523c5ae46ab7492586044ff5fabea32" alt="Full screen mode with data visualization" width="1999" height="1423" data-path="images/mcp-apps/fullscreen-2.png" />
109![Full screen mode with data visualization](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/fullscreen-2.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=8523c5ae46ab7492586044ff5fabea32)
110110 
111111Full screen mode has these constraints:
112112 
from line 117
117117 
118118On mobile, your app fills the entire screen with the chat input and navigation bar overlaid on top, so keep critical UI within the safe area. Use the full viewport width, and support both portrait and landscape where it makes sense.
119119 
120<img src="https://mintcdn.com/claude-ai/sLZLADaApRAVEV6C/images/mcp-apps/fullscreen-mobile.png?fit=max&auto=format&n=sLZLADaApRAVEV6C&q=85&s=d2802346090fd1a7af6a1bdc1d7c3263" alt="Full screen mobile examples" width="1999" height="716" data-path="images/mcp-apps/fullscreen-mobile.png" />
120![Full screen mobile examples](https://mintcdn.com/claude-ai/sLZLADaApRAVEV6C/images/mcp-apps/fullscreen-mobile.png?fit=max\&auto=format\&n=sLZLADaApRAVEV6C\&q=85\&s=d2802346090fd1a7af6a1bdc1d7c3263)
121121 
122122## Mobile guidelines
123123 
from line 133
133133 
134134Read `hostContext.safeAreaInsets.{top, right, bottom, left}`, which are pixel values, and apply them as padding on your root container, or as `scroll-padding` on scroll-snap containers so items come to rest inside the visible region. Safe areas aren't mobile-specific: on web and desktop the composer can overlay the bottom of an inline app, so avoid placing interactive controls flush against any edge.
135135 
136<img src="https://mintcdn.com/claude-ai/sLZLADaApRAVEV6C/images/mcp-apps/safe-area-fullscreen.png?fit=max&auto=format&n=sLZLADaApRAVEV6C&q=85&s=d789bcbba50a3bff53d677478421de30" alt="Safe area insets in full screen mode on web and mobile" width="1999" height="1153" data-path="images/mcp-apps/safe-area-fullscreen.png" />
136![Safe area insets in full screen mode on web and mobile](https://mintcdn.com/claude-ai/sLZLADaApRAVEV6C/images/mcp-apps/safe-area-fullscreen.png?fit=max\&auto=format\&n=sLZLADaApRAVEV6C\&q=85\&s=d789bcbba50a3bff53d677478421de30)
137137 
138138#### Borderless inline content
139139 
from line 141
141141 
142142In borderless mode your content runs edge-to-edge with no host padding, so honoring `safeAreaInsets` becomes essential. The bordered card's built-in padding otherwise absorbs most of them. Borderless works well for carousels and other horizontally scrolling content that should bleed to the screen edges while in motion: apply `safeAreaInsets.left` and `.right` as `scroll-padding-inline` on the scroll container so items at rest sit clear of the device edges but can scroll underneath them.
143143 
144<img src="https://mintcdn.com/claude-ai/sLZLADaApRAVEV6C/images/mcp-apps/safe-area-borderless-mobile.png?fit=max&auto=format&n=sLZLADaApRAVEV6C&q=85&s=9936d8064beb2fc37e39b10ab1e727a9" alt="Safe area insets for borderless inline apps on mobile" width="1999" height="1857" data-path="images/mcp-apps/safe-area-borderless-mobile.png" />
144![Safe area insets for borderless inline apps on mobile](https://mintcdn.com/claude-ai/sLZLADaApRAVEV6C/images/mcp-apps/safe-area-borderless-mobile.png?fit=max\&auto=format\&n=sLZLADaApRAVEV6C\&q=85\&s=9936d8064beb2fc37e39b10ab1e727a9)
145145 
146146### Declare supported display modes
147147 
from line 174
174174* Use full-width layouts, without side margins that waste mobile screen space
175175* Let content reflow gracefully, and avoid fixed-width layouts
176176 
177<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/mobile-viewport-layout.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=d2244b6ff7e2d6c5e7b2c36fd2d901ed" alt="Viewport and layout do's and don'ts" width="1718" height="1238" data-path="images/mcp-apps/mobile-viewport-layout.png" />
177![Viewport and layout do's and don'ts](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/mobile-viewport-layout.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=d2244b6ff7e2d6c5e7b2c36fd2d901ed)
178178 
179179### Touch targets
180180 
from line 183
183183* Larger, thumb-friendly buttons rather than small text links
184184* Primary actions within natural thumb reach, in the lower portion of the screen
185185 
186<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/mobile-touch-targets.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=95bd5b4393d85a67b74e894331d5f4b0" alt="Touch target do's and don'ts" width="1718" height="1238" data-path="images/mcp-apps/mobile-touch-targets.png" />
186![Touch target do's and don'ts](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/mobile-touch-targets.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=95bd5b4393d85a67b74e894331d5f4b0)
187187 
188188### Scrolling and gestures
189189 
from line 199
199199* Provide a clear visual affordance for expansion, such as a fullscreen button or tap-to-expand
200200* Closing fullscreen returns to the conversation at the same scroll position
201201 
202<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/mobile-transitions.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=343fe75a1428c1874568c10e02de9aa8" alt="Transition from inline card to full screen" width="1718" height="1222" data-path="images/mcp-apps/mobile-transitions.png" />
202![Transition from inline card to full screen](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/mobile-transitions.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=343fe75a1428c1874568c10e02de9aa8)
203203 
204204### Dark mode
205205 
206206All views must support both light and dark themes. Use the host's style tokens, which adapt automatically, and never hardcode colors. Test both modes.
207207 
208<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/mobile-dark-mode.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=79df1abf42e75b052ad7c28ac71c4413" alt="Dark mode examples on mobile" width="1999" height="1315" data-path="images/mcp-apps/mobile-dark-mode.png" />
208![Dark mode examples on mobile](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/mobile-dark-mode.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=79df1abf42e75b052ad7c28ac71c4413)
209209 
210210### Loading states
211211 
212212Show skeleton screens while content loads, and match the layout structure of the final content so the swap is smooth. Avoid spinners for inline content, because skeletons feel more native.
213213 
214<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/mobile-loading-states.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=9e7df0fa2294bbcbcc24bf079cbc5ff6" alt="Loading state examples on mobile" width="1718" height="1130" data-path="images/mcp-apps/mobile-loading-states.png" />
214![Loading state examples on mobile](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/mobile-loading-states.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=9e7df0fa2294bbcbcc24bf079cbc5ff6)
215215 
216216## Visual design
217217 
from line 221
221221 
222222Use host tokens for all structural elements: backgrounds, text, borders, and icons. You can use your own brand colors for accents and identity, but the core UI should use the provided palette.
223223 
224<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/color-tokens.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=173ce1948ae87b9e72b0854b5d27f4e1" alt="Color token examples for light and dark mode" width="1999" height="1863" data-path="images/mcp-apps/color-tokens.png" />
224![Color token examples for light and dark mode](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/color-tokens.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=173ce1948ae87b9e72b0854b5d27f4e1)
225225 
226226### Typography
227227 
228228Stick to the three-level size scale of heading, body, and caption, and the two weights of regular and emphasized. This creates clear hierarchy without visual noise.
229229 
230<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/typography.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=51ce152aab3d975ae57c5e5792dc6097" alt="Typography scale examples" width="1999" height="936" data-path="images/mcp-apps/typography.png" />
230![Typography scale examples](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/typography.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=51ce152aab3d975ae57c5e5792dc6097)
231231 
232<Note>
233 The Figma Community File uses Anthropic Sans and Anthropic Serif. When your app runs inside Claude, the host client provides Anthropic Sans at runtime through style variables. For local development, [download and install the fonts](https://brand.anthropic.com/typography).
234</Note>
232> [!NOTE]
233>
234> The Figma Community File uses Anthropic Sans and Anthropic Serif. When your app runs inside Claude, the host client provides Anthropic Sans at runtime through style variables. For local development, [download and install the fonts](https://brand.anthropic.com/typography).
235235 
236236### Borders
237237 
238238Use a limited set of corner radii and border thicknesses to keep your app feeling native to the surrounding UI.
239239 
240<img src="https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/borders.png?fit=max&auto=format&n=IPtAfld1XUBVOx8m&q=85&s=1755cc995399c9d6bf25a1405ff139f4" alt="Border radius examples" width="1999" height="201" data-path="images/mcp-apps/borders.png" />
240![Border radius examples](https://mintcdn.com/claude-ai/IPtAfld1XUBVOx8m/images/mcp-apps/borders.png?fit=max\&auto=format\&n=IPtAfld1XUBVOx8m\&q=85\&s=1755cc995399c9d6bf25a1405ff139f4)
241241 
242242### Icons
243243 
244244Use monochromatic, outlined icons that match the host's icon color tokens. Icons should support understanding rather than be essential to it.
245245 
246<img src="https://mintcdn.com/claude-ai/IXMfN0TT8kfXbN6T/images/mcp-apps/icons.png?fit=max&auto=format&n=IXMfN0TT8kfXbN6T&q=85&s=1454f1f3e5e13107cbe543e8d6d617df" alt="Icon style examples" width="1320" height="768" data-path="images/mcp-apps/icons.png" />
246![Icon style examples](https://mintcdn.com/claude-ai/IXMfN0TT8kfXbN6T/images/mcp-apps/icons.png?fit=max\&auto=format\&n=IXMfN0TT8kfXbN6T\&q=85\&s=1454f1f3e5e13107cbe543e8d6d617df)
247247 
248248### Spacing
249249 

connectors/building/mcp-apps/external-links Changed · +3 / -3 lines

from line 55
5555 
5656If your app sends `ui/open-link` without a preceding gesture, for example programmatically, on a timer, or after the browser's activation window has expired, Claude shows the modal so the user's confirmation click supplies the gesture the browser requires to open a new tab.
5757 
58<Note>
59 A bypassed `ui/open-link` request resolves successfully once the open is attempted. It doesn't indicate whether the browser actually opened the tab, so don't treat the response as confirmation that the user reached the destination.
60</Note>
58> [!NOTE]
59>
60> A bypassed `ui/open-link` request resolves successfully once the open is attempted. It doesn't indicate whether the browser actually opened the tab, so don't treat the response as confirmation that the user reached the destination.
6161 
6262## Design for the modal
6363 

connectors/building/mcp-apps/getting-started Changed · +65 / -67 lines

from line 6
66 
77This page is for developers who have an MCP server, or are building one, and want it to show UI in Claude.
88 
9<Note>
10 If you haven't built the server yet, see [Build an MCP server for Claude](/docs/connectors/building/index).
11</Note>
9> [!NOTE]
10>
11> If you haven't built the server yet, see [Build an MCP server for Claude](/docs/connectors/building/index).
1212 
1313To see how an MCP App looks and behaves, [try an example in Claude Desktop](#try-an-example-mcp-app-in-claude-desktop) first, then [build your own](#build-your-own-mcp-app) from the SDK quickstart, the examples, or the agent skills.
1414 
from line 20
2020 
2121Connecting an example server means adding its entry to Claude Desktop's configuration file, so that the desktop app runs the server locally with `npx`. To connect one:
2222 
23<Steps>
24 <Step title="Install Claude Desktop">
25 Install Claude Desktop and sign in.
26 </Step>
23**1. Install Claude Desktop**
2724 
28 <Step title="Open the configuration file">
29 Go to [**Settings > Developer**](https://claude.ai/desktop/settings/desktop/developer) and click **Edit Config**.
30 </Step>
25Install Claude Desktop and sign in.
3126 
32 <Step title="Add an example server">
33 Add one of these example servers to your `claude_desktop_config.json`:
27**2. Open the configuration file**
3428 
35 | Example | Description |
36 | - | - |
37 | [Customer Segmentation](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/customer-segmentation-server) | Data visualization with scatter charts and clustering analysis |
38 | [Map](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) | Interactive 3D globe viewer using CesiumJS |
39 | [ShaderToy](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/shadertoy-server) | Real-time GLSL shader compilation and display |
40 | [Sheet Music](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server) | ABC notation rendering with interactive audio playback |
29Go to [**Settings > Developer**](https://claude.ai/desktop/settings/desktop/developer) and click **Edit Config**.
4130 
42 The MCP Apps repository has [more examples](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples), each with a ready-to-use config snippet. The config entry for each example in the table runs its server with `npx`, which fetches the latest published version each time. Pin a version, such as `@modelcontextprotocol/[email protected]`, if you keep an entry beyond trying it out:
31**3. Add an example server**
4332 
44 <CodeGroup>
45 ```json Customer Segmentation theme={null}
46 {
47 "mcpServers": {
48 "customer-segmentation": {
49 "command": "npx",
50 "args": ["-y", "@modelcontextprotocol/server-customer-segmentation", "--stdio"]
51 }
52 }
53 }
54 ```
33Add one of these example servers to your `claude_desktop_config.json`:
5534 
56 ```json Map theme={null}
57 {
58 "mcpServers": {
59 "map": {
60 "command": "npx",
61 "args": ["-y", "@modelcontextprotocol/server-map", "--stdio"]
62 }
63 }
64 }
65 ```
35| Example | Description |
36| - | - |
37| [Customer Segmentation](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/customer-segmentation-server) | Data visualization with scatter charts and clustering analysis |
38| [Map](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) | Interactive 3D globe viewer using CesiumJS |
39| [ShaderToy](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/shadertoy-server) | Real-time GLSL shader compilation and display |
40| [Sheet Music](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server) | ABC notation rendering with interactive audio playback |
6641 
67 ```json ShaderToy theme={null}
68 {
69 "mcpServers": {
70 "shadertoy": {
71 "command": "npx",
72 "args": ["-y", "@modelcontextprotocol/server-shadertoy", "--stdio"]
73 }
74 }
75 }
76 ```
42The MCP Apps repository has [more examples](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples), each with a ready-to-use config snippet. The config entry for each example in the table runs its server with `npx`, which fetches the latest published version each time. Pin a version, such as `@modelcontextprotocol/[email protected]`, if you keep an entry beyond trying it out:
7743 
78 ```json Sheet Music theme={null}
79 {
80 "mcpServers": {
81 "sheet-music": {
82 "command": "npx",
83 "args": ["-y", "@modelcontextprotocol/server-sheet-music", "--stdio"]
84 }
85 }
86 }
87 ```
88 </CodeGroup>
89 </Step>
44```json Customer Segmentation theme={null}
45{
46 "mcpServers": {
47 "customer-segmentation": {
48 "command": "npx",
49 "args": ["-y", "@modelcontextprotocol/server-customer-segmentation", "--stdio"]
50 }
51 }
52}
53```
9054 
91 <Step title="Save and restart">
92 Save the file and restart the desktop app to connect the server.
93 </Step>
94</Steps>
55```json Map theme={null}
56{
57 "mcpServers": {
58 "map": {
59 "command": "npx",
60 "args": ["-y", "@modelcontextprotocol/server-map", "--stdio"]
61 }
62 }
63}
64```
9565 
66```json ShaderToy theme={null}
67{
68 "mcpServers": {
69 "shadertoy": {
70 "command": "npx",
71 "args": ["-y", "@modelcontextprotocol/server-shadertoy", "--stdio"]
72 }
73 }
74}
75```
76 
77```json Sheet Music theme={null}
78{
79 "mcpServers": {
80 "sheet-music": {
81 "command": "npx",
82 "args": ["-y", "@modelcontextprotocol/server-sheet-music", "--stdio"]
83 }
84 }
85}
86```
87 
88**4. Save and restart**
89 
90Save the file and restart the desktop app to connect the server.
91 
9692### Ask Claude to use the app
9793 
9894Once the server is connected, prompt Claude to use it. For example, with the customer segmentation server connected, ask Claude to show you recent customer data.
from line 104
108104* [SDK API documentation](https://modelcontextprotocol.github.io/ext-apps/api/index.html): full API reference
109105* [Example implementations](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples): vanilla JS, React, Vue, Svelte, and more
110106 
111<Tip>To test a remote MCP App locally, connect to it through a proxy such as [mcp-remote](https://www.npmjs.com/package/mcp-remote).</Tip>
107> [!TIP]
108>
109> To test a remote MCP App locally, connect to it through a proxy such as [mcp-remote](https://www.npmjs.com/package/mcp-remote).
112110 
113111### Register tools and resources once for every host
114112 

connectors/building/mcp-apps/quickstart Changed · +255 / -259 lines

The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.

from line 6
66 
77This quickstart is for developers who finished the first quickstart and have its server on their machine. It stops at what you can verify locally: seeing the UI render needs the server hosted where Claude can reach it, which the last section covers.
88 
9<Note>
10 * If you haven't built the quickstart server yet, start with [Build your first MCP server for Claude](/docs/connectors/building/quickstart)
11 * If you want to see finished MCP Apps running in Claude first, see [Try an example MCP App in Claude Desktop](/docs/connectors/building/mcp-apps/getting-started#try-an-example-mcp-app-in-claude-desktop)
12 * If you're adding UI to your own production server, see [Build your own MCP App](/docs/connectors/building/mcp-apps/getting-started#build-your-own-mcp-app) for the SDK documentation and full examples
13</Note>
9> [!NOTE]
10>
11> * If you haven't built the quickstart server yet, start with [Build your first MCP server for Claude](/docs/connectors/building/quickstart)
12> * If you want to see finished MCP Apps running in Claude first, see [Try an example MCP App in Claude Desktop](/docs/connectors/building/mcp-apps/getting-started#try-an-example-mcp-app-in-claude-desktop)
13> * If you're adding UI to your own production server, see [Build your own MCP App](/docs/connectors/building/mcp-apps/getting-started#build-your-own-mcp-app) for the SDK documentation and full examples
1414 
1515## Install the MCP Apps SDK
1616 
from line 39
3939 
4040Make these edits to `server.mjs` in order. If you'd rather paste the whole file, it's in [The complete file](#the-complete-file) at the end of this section.
4141 
42<Steps>
43 <Step title="Import the server helpers">
44 Add one import under the existing SDK imports at the top of `server.mjs`. `registerAppTool` and `registerAppResource` wrap the SDK's own `registerTool` and `registerResource` and fill in the UI metadata for you:
45 
46 ```js server.mjs {4} theme={null}
47 import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
48 import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
49 import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
50 import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
51 import { z } from 'zod';
52 ```
53 </Step>
54 
55 <Step title="Write the UI as an HTML string">
56 Below the imports and above `function buildServer()`, add the resource URI and the HTML the host will render. The HTML is ordinary markup plus one `<script type="module">`. The highlighted lines are the ones that make it an MCP App: the script loads the SDK's browser client, `App`, from a CDN copy of the version you installed, registers handlers for the tool's input and result, wires the button to call the tool again through the host, and then connects:
57 
58 ```js server.mjs {1,16,18,26-29} theme={null}
59 const DICE_UI = 'ui://quickstart/dice'; // MCP App resources use the ui:// scheme
60 
61 const DICE_HTML = `<!DOCTYPE html>
62 <html><head><meta charset="utf-8"><meta name="color-scheme" content="light dark"><title>Dice</title>
63 <style>
64 body { font: 14px system-ui, sans-serif; margin: 0; padding: 12px; }
65 #dice { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
66 .die { width: 44px; height: 44px; border: 2px solid; border-radius: 10px; display: grid; place-items: center; font-size: 18px; font-weight: 600; }
67 </style></head>
68 <body>
69 <div id="summary">Waiting for a roll…</div>
70 <div id="dice"></div>
71 <button id="again" disabled>Roll again</button>
72 <script type="module">
73 // The in-frame client. Loading the prebuilt bundle from a CDN means no bundler in this project.
74 import { App } from "https://unpkg.com/@modelcontextprotocol/[email protected]/dist/src/app-with-deps.js";
75 const $ = (id) => document.getElementById(id);
76 const app = new App({ name: "Dice View", version: "1.0.0" });
77 let lastArgs = { sides: 6, count: 1 };
78 const show = ({ structuredContent: sc, content }) => {
79 $("summary").textContent = sc ? sc.count + "d" + sc.sides + " → total " + sc.total : (content?.[0]?.text ?? "?");
80 $("dice").replaceChildren(...(sc?.rolls ?? []).map((n) => Object.assign(document.createElement("div"), { className: "die", textContent: n })));
81 $("again").disabled = false;
42**1. Import the server helpers**
43 
44Add one import under the existing SDK imports at the top of `server.mjs`. `registerAppTool` and `registerAppResource` wrap the SDK's own `registerTool` and `registerResource` and fill in the UI metadata for you:
45 
46```js server.mjs {4} theme={null}
47import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
48import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
49import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
50import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
51import { z } from 'zod';
52```
53 
54**2. Write the UI as an HTML string**
55 
56Below the imports and above `function buildServer()`, add the resource URI and the HTML the host will render. The HTML is ordinary markup plus one `<script type="module">`. The highlighted lines are the ones that make it an MCP App: the script loads the SDK's browser client, `App`, from a CDN copy of the version you installed, registers handlers for the tool's input and result, wires the button to call the tool again through the host, and then connects:
57 
58```js server.mjs {1,16,18,26-29} theme={null}
59const DICE_UI = 'ui://quickstart/dice'; // MCP App resources use the ui:// scheme
60 
61const DICE_HTML = `<!DOCTYPE html>
62<html><head><meta charset="utf-8"><meta name="color-scheme" content="light dark"><title>Dice</title>
63<style>
64 body { font: 14px system-ui, sans-serif; margin: 0; padding: 12px; }
65 #dice { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
66 .die { width: 44px; height: 44px; border: 2px solid; border-radius: 10px; display: grid; place-items: center; font-size: 18px; font-weight: 600; }
67</style></head>
68<body>
69<div id="summary">Waiting for a roll…</div>
70<div id="dice"></div>
71<button id="again" disabled>Roll again</button>
72<script type="module">
73 // The in-frame client. Loading the prebuilt bundle from a CDN means no bundler in this project.
74 import { App } from "https://unpkg.com/@modelcontextprotocol/[email protected]/dist/src/app-with-deps.js";
75 const $ = (id) => document.getElementById(id);
76 const app = new App({ name: "Dice View", version: "1.0.0" });
77 let lastArgs = { sides: 6, count: 1 };
78 const show = ({ structuredContent: sc, content }) => {
79 $("summary").textContent = sc ? sc.count + "d" + sc.sides + " → total " + sc.total : (content?.[0]?.text ?? "?");
80 $("dice").replaceChildren(...(sc?.rolls ?? []).map((n) => Object.assign(document.createElement("div"), { className: "die", textContent: n })));
81 $("again").disabled = false;
82 };
83 // Set handlers before connect() so the first tool-input and tool-result notifications aren't missed.
84 app.ontoolinput = ({ arguments: args }) => { if (args) lastArgs = { ...lastArgs, ...args }; };
85 app.ontoolresult = show; // the host pushes the tool result here; draw it
86 $("again").onclick = async () => show(await app.callServerTool({ name: "roll_dice", arguments: lastArgs }));
87 await app.connect(); // handshake with the host
88</script>
89</body></html>`;
90```
91 
92If you change the installed `ext-apps` version, change the version in the `unpkg.com` URL to match.
93 
94**3. Link the tool to the UI**
95 
96Inside `buildServer()`, replace the `server.registerTool(` call with `registerAppTool(server,` and make two additions, both highlighted: a `_meta.ui.resourceUri` entry that names the UI resource, and a `structuredContent` object in the result. The UI reads `structuredContent` to draw the dice, and the `content` text stays for hosts that don't render UI:
97 
98```js server.mjs {1-2,11,17-18} theme={null}
99 registerAppTool(
100 server,
101 'roll_dice',
102 {
103 title: 'Roll dice',
104 description: 'Roll `count` dice with `sides` sides each. Returns each roll and the total.',
105 inputSchema: {
106 sides: z.number().int().min(2).max(100).describe('Number of sides per die (2-100)'),
107 count: z.number().int().min(1).max(10).default(1).describe('How many dice to roll (1-10)'),
108 },
109 _meta: { ui: { resourceUri: DICE_UI } }, // tells the host which resource renders this tool
110 },
111 async ({ sides, count }) => {
112 const rolls = Array.from({ length: count }, () => 1 + Math.floor(Math.random() * sides));
113 const total = rolls.reduce((a, b) => a + b, 0);
114 return {
115 content: [{ type: 'text', text: `Rolled ${count}d${sides}: [${rolls.join(', ')}] total=${total}` }], // text-only hosts read this
116 structuredContent: { sides, count, rolls, total }, // the UI reads this
82117 };
83 // Set handlers before connect() so the first tool-input and tool-result notifications aren't missed.
84 app.ontoolinput = ({ arguments: args }) => { if (args) lastArgs = { ...lastArgs, ...args }; };
85 app.ontoolresult = show; // the host pushes the tool result here; draw it
86 $("again").onclick = async () => show(await app.callServerTool({ name: "roll_dice", arguments: lastArgs }));
87 await app.connect(); // handshake with the host
88 </script>
89 </body></html>`;
90 ```
91 
92 If you change the installed `ext-apps` version, change the version in the `unpkg.com` URL to match.
93 </Step>
94 
95 <Step title="Link the tool to the UI">
96 Inside `buildServer()`, replace the `server.registerTool(` call with `registerAppTool(server,` and make two additions, both highlighted: a `_meta.ui.resourceUri` entry that names the UI resource, and a `structuredContent` object in the result. The UI reads `structuredContent` to draw the dice, and the `content` text stays for hosts that don't render UI:
97 
98 ```js server.mjs {1-2,11,17-18} theme={null}
99 registerAppTool(
100 server,
101 'roll_dice',
102 {
103 title: 'Roll dice',
104 description: 'Roll `count` dice with `sides` sides each. Returns each roll and the total.',
105 inputSchema: {
106 sides: z.number().int().min(2).max(100).describe('Number of sides per die (2-100)'),
107 count: z.number().int().min(1).max(10).default(1).describe('How many dice to roll (1-10)'),
108 },
109 _meta: { ui: { resourceUri: DICE_UI } }, // tells the host which resource renders this tool
110 },
111 async ({ sides, count }) => {
112 const rolls = Array.from({ length: count }, () => 1 + Math.floor(Math.random() * sides));
113 const total = rolls.reduce((a, b) => a + b, 0);
114 return {
115 content: [{ type: 'text', text: `Rolled ${count}d${sides}: [${rolls.join(', ')}] total=${total}` }], // text-only hosts read this
116 structuredContent: { sides, count, rolls, total }, // the UI reads this
117 };
118 },
119 );
120 ```
121 </Step>
122 
123 <Step title="Register the UI resource">
124 Still inside `buildServer()`, before `return server;`, register the resource that serves the HTML. `registerAppResource` sets the MIME type an MCP App resource must have, `text/html;profile=mcp-app`, which the SDK exports as `RESOURCE_MIME_TYPE`. The highlighted `csp` line tells the host's sandbox to allow scripts from `unpkg.com`, because by default the frame only runs scripts that are inline or from its own origin, and the `App` import would be blocked. For production, serve the client script from your own origin, or bundle it into the HTML, and list only that origin in `resourceDomains`:
125 
126 ```js server.mjs {4,6} theme={null}
127 registerAppResource(server, 'Dice view', DICE_UI, { description: 'Interactive view for roll_dice' }, async () => ({
128 contents: [{
129 uri: DICE_UI,
130 mimeType: RESOURCE_MIME_TYPE,
131 text: DICE_HTML,
132 _meta: { ui: { csp: { resourceDomains: ['https://unpkg.com'] } } }, // let the sandbox load the App script
133 }],
134 }));
135 return server;
136 ```
137 </Step>
138</Steps>
118 },
119 );
120```
121 
122**4. Register the UI resource**
123 
124Still inside `buildServer()`, before `return server;`, register the resource that serves the HTML. `registerAppResource` sets the MIME type an MCP App resource must have, `text/html;profile=mcp-app`, which the SDK exports as `RESOURCE_MIME_TYPE`. The highlighted `csp` line tells the host's sandbox to allow scripts from `unpkg.com`, because by default the frame only runs scripts that are inline or from its own origin, and the `App` import would be blocked. For production, serve the client script from your own origin, or bundle it into the HTML, and list only that origin in `resourceDomains`:
125 
126```js server.mjs {4,6} theme={null}
127 registerAppResource(server, 'Dice view', DICE_UI, { description: 'Interactive view for roll_dice' }, async () => ({
128 contents: [{
129 uri: DICE_UI,
130 mimeType: RESOURCE_MIME_TYPE,
131 text: DICE_HTML,
132 _meta: { ui: { csp: { resourceDomains: ['https://unpkg.com'] } } }, // let the sandbox load the App script
133 }],
134 }));
135 return server;
136```
139137 
140138Run `node --check server.mjs` to catch typos. It prints nothing when the file parses.
141139 
from line 141
143141 
144142For reference, this is `server.mjs` with every edit applied. Everything below `buildServer()` is unchanged from the first quickstart.
145143 
146<Accordion title="server.mjs with the MCP App added">
147 ```js server.mjs theme={null}
148 import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
149 import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
150 import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
151 import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
152 import { z } from 'zod';
153 
154 const DICE_UI = 'ui://quickstart/dice'; // MCP App resources use the ui:// scheme
155 
156 const DICE_HTML = `<!DOCTYPE html>
157 <html><head><meta charset="utf-8"><meta name="color-scheme" content="light dark"><title>Dice</title>
158 <style>
159 body { font: 14px system-ui, sans-serif; margin: 0; padding: 12px; }
160 #dice { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
161 .die { width: 44px; height: 44px; border: 2px solid; border-radius: 10px; display: grid; place-items: center; font-size: 18px; font-weight: 600; }
162 </style></head>
163 <body>
164 <div id="summary">Waiting for a roll…</div>
165 <div id="dice"></div>
166 <button id="again" disabled>Roll again</button>
167 <script type="module">
168 // The in-frame client. Loading the prebuilt bundle from a CDN means no bundler in this project.
169 import { App } from "https://unpkg.com/@modelcontextprotocol/[email protected]/dist/src/app-with-deps.js";
170 const $ = (id) => document.getElementById(id);
171 const app = new App({ name: "Dice View", version: "1.0.0" });
172 let lastArgs = { sides: 6, count: 1 };
173 const show = ({ structuredContent: sc, content }) => {
174 $("summary").textContent = sc ? sc.count + "d" + sc.sides + " → total " + sc.total : (content?.[0]?.text ?? "?");
175 $("dice").replaceChildren(...(sc?.rolls ?? []).map((n) => Object.assign(document.createElement("div"), { className: "die", textContent: n })));
176 $("again").disabled = false;
177 };
178 // Set handlers before connect() so the first tool-input and tool-result notifications aren't missed.
179 app.ontoolinput = ({ arguments: args }) => { if (args) lastArgs = { ...lastArgs, ...args }; };
180 app.ontoolresult = show; // the host pushes the tool result here; draw it
181 $("again").onclick = async () => show(await app.callServerTool({ name: "roll_dice", arguments: lastArgs }));
182 await app.connect(); // handshake with the host
183 </script>
184 </body></html>`;
185 
186 function buildServer() {
187 const server = new McpServer({ name: 'quickstart-server', version: '1.0.0' }); // the name Claude sees
188 
189 registerAppTool(
190 server,
191 'roll_dice',
192 {
193 title: 'Roll dice',
194 description: 'Roll `count` dice with `sides` sides each. Returns each roll and the total.',
195 inputSchema: {
196 sides: z.number().int().min(2).max(100).describe('Number of sides per die (2-100)'),
197 count: z.number().int().min(1).max(10).default(1).describe('How many dice to roll (1-10)'),
198 },
199 _meta: { ui: { resourceUri: DICE_UI } }, // tells the host which resource renders this tool
200 },
201 async ({ sides, count }) => {
202 const rolls = Array.from({ length: count }, () => 1 + Math.floor(Math.random() * sides));
203 const total = rolls.reduce((a, b) => a + b, 0);
204 return {
205 content: [{ type: 'text', text: `Rolled ${count}d${sides}: [${rolls.join(', ')}] total=${total}` }], // text-only hosts read this
206 structuredContent: { sides, count, rolls, total }, // the UI reads this
207 };
208 },
209 );
210 
211 registerAppResource(server, 'Dice view', DICE_UI, { description: 'Interactive view for roll_dice' }, async () => ({
212 contents: [{
213 uri: DICE_UI,
214 mimeType: RESOURCE_MIME_TYPE,
215 text: DICE_HTML,
216 _meta: { ui: { csp: { resourceDomains: ['https://unpkg.com'] } } }, // let the sandbox load the App script
217 }],
218 }));
219 return server;
220 }
221 
222 // Streamable HTTP: every MCP message arrives as a POST to /mcp. Stateless, so each request gets a fresh server.
223 const app = createMcpExpressApp(); // Express app with JSON parsing and localhost protection built in
224 app.post('/mcp', async (req, res) => {
225 const server = buildServer();
226 const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); // undefined = no sessions
227 res.on('close', () => { transport.close(); server.close(); });
228 await server.connect(transport);
229 await transport.handleRequest(req, res, req.body);
230 });
231 const notAllowed = (req, res) => res.status(405).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Method not allowed.' }, id: null });
232 app.get('/mcp', notAllowed);
233 app.delete('/mcp', notAllowed);
234 
235 const PORT = Number(process.env.PORT ?? 3000);
236 app.listen(PORT, '127.0.0.1', (err) => { // Express 5 reports a taken port here instead of throwing
237 if (err) { console.error(`failed to listen on ${PORT}: ${err.message}`); process.exit(1); }
238 console.log(`quickstart-server listening on http://localhost:${PORT}/mcp`);
239 });
240 ```
241</Accordion>
144> **server.mjs with the MCP App added**
145>
146> ```js server.mjs theme={null}
147> import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
148> import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
149> import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
150> import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
151> import { z } from 'zod';
152>
153> const DICE_UI = 'ui://quickstart/dice'; // MCP App resources use the ui:// scheme
154>
155> const DICE_HTML = `<!DOCTYPE html>
156> <html><head><meta charset="utf-8"><meta name="color-scheme" content="light dark"><title>Dice</title>
157> <style>
158> body { font: 14px system-ui, sans-serif; margin: 0; padding: 12px; }
159> #dice { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
160> .die { width: 44px; height: 44px; border: 2px solid; border-radius: 10px; display: grid; place-items: center; font-size: 18px; font-weight: 600; }
161> </style></head>
162> <body>
163> <div id="summary">Waiting for a roll…</div>
164> <div id="dice"></div>
165> <button id="again" disabled>Roll again</button>
166> <script type="module">
167> // The in-frame client. Loading the prebuilt bundle from a CDN means no bundler in this project.
168> import { App } from "https://unpkg.com/@modelcontextprotocol/[email protected]/dist/src/app-with-deps.js";
169> const $ = (id) => document.getElementById(id);
170> const app = new App({ name: "Dice View", version: "1.0.0" });
171> let lastArgs = { sides: 6, count: 1 };
172> const show = ({ structuredContent: sc, content }) => {
173> $("summary").textContent = sc ? sc.count + "d" + sc.sides + " → total " + sc.total : (content?.[0]?.text ?? "?");
174> $("dice").replaceChildren(...(sc?.rolls ?? []).map((n) => Object.assign(document.createElement("div"), { className: "die", textContent: n })));
175> $("again").disabled = false;
176> };
177> // Set handlers before connect() so the first tool-input and tool-result notifications aren't missed.
178> app.ontoolinput = ({ arguments: args }) => { if (args) lastArgs = { ...lastArgs, ...args }; };
179> app.ontoolresult = show; // the host pushes the tool result here; draw it
180> $("again").onclick = async () => show(await app.callServerTool({ name: "roll_dice", arguments: lastArgs }));
181> await app.connect(); // handshake with the host
182> </script>
183> </body></html>`;
184>
185> function buildServer() {
186> const server = new McpServer({ name: 'quickstart-server', version: '1.0.0' }); // the name Claude sees
187>
188> registerAppTool(
189> server,
190> 'roll_dice',
191> {
192> title: 'Roll dice',
193> description: 'Roll `count` dice with `sides` sides each. Returns each roll and the total.',
194> inputSchema: {
195> sides: z.number().int().min(2).max(100).describe('Number of sides per die (2-100)'),
196> count: z.number().int().min(1).max(10).default(1).describe('How many dice to roll (1-10)'),
197> },
198> _meta: { ui: { resourceUri: DICE_UI } }, // tells the host which resource renders this tool
199> },
200> async ({ sides, count }) => {
201> const rolls = Array.from({ length: count }, () => 1 + Math.floor(Math.random() * sides));
202> const total = rolls.reduce((a, b) => a + b, 0);
203> return {
204> content: [{ type: 'text', text: `Rolled ${count}d${sides}: [${rolls.join(', ')}] total=${total}` }], // text-only hosts read this
205> structuredContent: { sides, count, rolls, total }, // the UI reads this
206> };
207> },
208> );
209>
210> registerAppResource(server, 'Dice view', DICE_UI, { description: 'Interactive view for roll_dice' }, async () => ({
211> contents: [{
212> uri: DICE_UI,
213> mimeType: RESOURCE_MIME_TYPE,
214> text: DICE_HTML,
215> _meta: { ui: { csp: { resourceDomains: ['https://unpkg.com'] } } }, // let the sandbox load the App script
216> }],
217> }));
218> return server;
219> }
220>
221> // Streamable HTTP: every MCP message arrives as a POST to /mcp. Stateless, so each request gets a fresh server.
222> const app = createMcpExpressApp(); // Express app with JSON parsing and localhost protection built in
223> app.post('/mcp', async (req, res) => {
224> const server = buildServer();
225> const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); // undefined = no sessions
226> res.on('close', () => { transport.close(); server.close(); });
227> await server.connect(transport);
228> await transport.handleRequest(req, res, req.body);
229> });
230> const notAllowed = (req, res) => res.status(405).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Method not allowed.' }, id: null });
231> app.get('/mcp', notAllowed);
232> app.delete('/mcp', notAllowed);
233>
234> const PORT = Number(process.env.PORT ?? 3000);
235> app.listen(PORT, '127.0.0.1', (err) => { // Express 5 reports a taken port here instead of throwing
236> if (err) { console.error(`failed to listen on ${PORT}: ${err.message}`); process.exit(1); }
237> console.log(`quickstart-server listening on http://localhost:${PORT}/mcp`);
238> });
239> ```
242240 
243241## Check that the server advertises the UI
244242 
245243A host discovers the UI from the `_meta` on the tool in `tools/list` and loads it from `resources/read`, and your server now returns both. You can see both with `curl` before any host is involved.
246244 
247<Steps>
248 <Step title="Restart the server">
249 In the terminal where the server runs, press `Ctrl+C` to stop it, then start it again from the `mcp-quickstart` folder so it picks up your edits:
250 
251 ```bash theme={null}
252 node server.mjs
253 ```
254 
255 It prints `quickstart-server listening on http://localhost:3000/mcp` as before.
256 </Step>
257 
258 <Step title="Check the tool points at the UI">
259 In your second terminal, list the tools again:
260 
261 ```bash theme={null}
262 curl -sS -X POST http://localhost:3000/mcp \
263 -H 'Content-Type: application/json' \
264 -H 'Accept: application/json, text/event-stream' \
265 -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
266 ```
267 
268 The `roll_dice` entry now ends with a `_meta` object naming the resource. The helper also writes the same URI under the legacy flat key `ui/resourceUri`. The rest of the entry is shortened to `...` here:
269 
270 ```text theme={null}
271 data: {"result":{"tools":[{"name":"roll_dice", ... ,"_meta":{"ui":{"resourceUri":"ui://quickstart/dice"},"ui/resourceUri":"ui://quickstart/dice"}}]},"jsonrpc":"2.0","id":2}
272 ```
273 </Step>
274 
275 <Step title="Read the UI resource">
276 Fetch the resource the way a host does, by its `ui://` URI:
277 
278 ```bash theme={null}
279 curl -sS -X POST http://localhost:3000/mcp \
280 -H 'Content-Type: application/json' \
281 -H 'Accept: application/json, text/event-stream' \
282 -d '{"jsonrpc":"2.0","id":4,"method":"resources/read","params":{"uri":"ui://quickstart/dice"}}'
283 ```
284 
285 The reply carries the MCP App MIME type and your HTML as `text`, shortened here after the opening tags:
286 
287 ```text theme={null}
288 data: {"result":{"contents":[{"uri":"ui://quickstart/dice","mimeType":"text/html;profile=mcp-app","text":"<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"> ...
289 ```
290 </Step>
291 
292 <Step title="Call the tool and see the structured result">
293 Call `roll_dice` once more:
294 
295 ```bash theme={null}
296 curl -sS -X POST http://localhost:3000/mcp \
297 -H 'Content-Type: application/json' \
298 -H 'Accept: application/json, text/event-stream' \
299 -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"roll_dice","arguments":{"sides":6,"count":4}}}'
300 ```
301 
302 The result has both the text block and the `structuredContent` object the UI draws from:
303 
304 ```text theme={null}
305 data: {"result":{"content":[{"type":"text","text":"Rolled 4d6: [1, 3, 1, 2] total=7"}],"structuredContent":{"sides":6,"count":4,"rolls":[1,3,1,2],"total":7}},"jsonrpc":"2.0","id":5}
306 ```
307 </Step>
308</Steps>
245**1. Restart the server**
246 
247In the terminal where the server runs, press `Ctrl+C` to stop it, then start it again from the `mcp-quickstart` folder so it picks up your edits:
248 
249```bash theme={null}
250node server.mjs
251```
252 
253It prints `quickstart-server listening on http://localhost:3000/mcp` as before.
254 
255**2. Check the tool points at the UI**
256 
257In your second terminal, list the tools again:
258 
259```bash theme={null}
260curl -sS -X POST http://localhost:3000/mcp \
261 -H 'Content-Type: application/json' \
262 -H 'Accept: application/json, text/event-stream' \
263 -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
264```
265 
266The `roll_dice` entry now ends with a `_meta` object naming the resource. The helper also writes the same URI under the legacy flat key `ui/resourceUri`. The rest of the entry is shortened to `...` here:
267 
268```text theme={null}
269data: {"result":{"tools":[{"name":"roll_dice", ... ,"_meta":{"ui":{"resourceUri":"ui://quickstart/dice"},"ui/resourceUri":"ui://quickstart/dice"}}]},"jsonrpc":"2.0","id":2}
270```
271 
272**3. Read the UI resource**
273 
274Fetch the resource the way a host does, by its `ui://` URI:
275 
276```bash theme={null}
277curl -sS -X POST http://localhost:3000/mcp \
278 -H 'Content-Type: application/json' \
279 -H 'Accept: application/json, text/event-stream' \
280 -d '{"jsonrpc":"2.0","id":4,"method":"resources/read","params":{"uri":"ui://quickstart/dice"}}'
281```
282 
283The reply carries the MCP App MIME type and your HTML as `text`, shortened here after the opening tags:
284 
285```text theme={null}
286data: {"result":{"contents":[{"uri":"ui://quickstart/dice","mimeType":"text/html;profile=mcp-app","text":"<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"> ...
287```
288 
289**4. Call the tool and see the structured result**
290 
291Call `roll_dice` once more:
292 
293```bash theme={null}
294curl -sS -X POST http://localhost:3000/mcp \
295 -H 'Content-Type: application/json' \
296 -H 'Accept: application/json, text/event-stream' \
297 -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"roll_dice","arguments":{"sides":6,"count":4}}}'
298```
299 
300The result has both the text block and the `structuredContent` object the UI draws from:
301 
302```text theme={null}
303data: {"result":{"content":[{"type":"text","text":"Rolled 4d6: [1, 3, 1, 2] total=7"}],"structuredContent":{"sides":6,"count":4,"rolls":[1,3,1,2],"total":7}},"jsonrpc":"2.0","id":5}
304```
309305 
310306Claude Code still works with the server after these edits, as a text-only client: `claude mcp list` shows `quickstart` connected, and the same `claude -p` prompt from the first quickstart calls the tool and reports the rolls. One difference shows up in the `stream-json` output: because the tool now returns `structuredContent`, the `tool_result` Claude receives is that JSON object as a string, such as `{"sides":20,"count":3,"rolls":[7,11,12],"total":30}`, rather than your `content` text.
311307 
312308 

connectors/building/mcp-apps/transparent-theming Changed · +35 / -37 lines

from line 86
8686 
8787This example applies the theme, variables, and fonts on connect and again on every host context change:
8888 
89<CodeGroup>
90 ```ts TypeScript theme={null}
91 import {
92 App,
93 applyDocumentTheme,
94 applyHostFonts,
95 applyHostStyleVariables,
96 type McpUiHostContext,
97 } from "@modelcontextprotocol/ext-apps";
89```ts TypeScript theme={null}
90import {
91 App,
92 applyDocumentTheme,
93 applyHostFonts,
94 applyHostStyleVariables,
95 type McpUiHostContext,
96} from "@modelcontextprotocol/ext-apps";
9897 
99 function applyHostContext(ctx: Partial<McpUiHostContext>) {
100 if (ctx.theme) applyDocumentTheme(ctx.theme);
101 if (ctx.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
102 if (ctx.styles?.css?.fonts) applyHostFonts(ctx.styles.css.fonts);
103 }
98function applyHostContext(ctx: Partial<McpUiHostContext>) {
99 if (ctx.theme) applyDocumentTheme(ctx.theme);
100 if (ctx.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
101 if (ctx.styles?.css?.fonts) applyHostFonts(ctx.styles.css.fonts);
102}
104103 
105 const app = new App({ name: "my-app", version: "1.0.0" });
104const app = new App({ name: "my-app", version: "1.0.0" });
106105 
107 // Updates carry only the fields that changed.
108 app.addEventListener("hostcontextchanged", (changed) => applyHostContext(changed));
106// Updates carry only the fields that changed.
107app.addEventListener("hostcontextchanged", (changed) => applyHostContext(changed));
109108 
110 await app.connect();
111 const initial = app.getHostContext();
112 if (initial) applyHostContext(initial);
113 ```
109await app.connect();
110const initial = app.getHostContext();
111if (initial) applyHostContext(initial);
112```
114113 
115 ```tsx React theme={null}
116 import { useApp, useHostStyles } from "@modelcontextprotocol/ext-apps/react";
114```tsx React theme={null}
115import { useApp, useHostStyles } from "@modelcontextprotocol/ext-apps/react";
117116 
118 function Widget() {
119 const { app } = useApp({
120 appInfo: { name: "my-app", version: "1.0.0" },
121 capabilities: {},
122 });
123 // Applies theme + CSS variables + fonts, and re-applies on host-context-changed.
124 useHostStyles(app, app?.getHostContext());
125 return <div className="card">…</div>;
126 }
127 ```
128</CodeGroup>
117function Widget() {
118 const { app } = useApp({
119 appInfo: { name: "my-app", version: "1.0.0" },
120 capabilities: {},
121 });
122 // Applies theme + CSS variables + fonts, and re-applies on host-context-changed.
123 useHostStyles(app, app?.getHostContext());
124 return <div className="card">…</div>;
125}
126```
129127 
130128### Reference the variables in your CSS
131129 
from line 140
142140}
143141```
144142 
145<Note>
146 Claude's token values use CSS `light-dark()`, so once `applyDocumentTheme` has set the root `color-scheme`, every `--color-*` variable resolves to the right variant without any `[data-theme]` selectors on your side.
147</Note>
143> [!NOTE]
144>
145> Claude's token values use CSS `light-dark()`, so once `applyDocumentTheme` has set the root `color-scheme`, every `--color-*` variable resolves to the right variant without any `[data-theme]` selectors on your side.
148146 
149147### Allow the host font origin in your CSP
150148 

connectors/building/mcp-apps/troubleshooting Changed · +45 / -41 lines

from line 12
1212 
1313Claude Desktop's Developer Tools can help you debug MCP Apps. To use them:
1414 
15<Steps>
16 <Step title="Enable Developer Mode">
17 Open **Help > Troubleshooting** and click **Enable Developer Mode**. A new **Developer** menu appears in the menu bar.
18 </Step>
15**1. Enable Developer Mode**
1916 
20 <Step title="Open Developer Tools">
21 Open Developer Tools by pressing `Cmd+Option+I` on Mac or `Ctrl+Shift+I` on Windows.
22 </Step>
17Open **Help > Troubleshooting** and click **Enable Developer Mode**. A new **Developer** menu appears in the menu bar.
2318 
24 <Step title="Find your app's iframe">
25 Inspect the tool call element and look for an iframe nested inside another iframe. Your app is loaded as the content of the inner iframe.
26 </Step>
27</Steps>
19**2. Open Developer Tools**
2820 
29<Tip>From the **Developer** menu, select **Reload MCP Configuration** after editing your `claude_desktop_config.json` to apply changes without restarting.</Tip>
21Open Developer Tools by pressing `Cmd+Option+I` on Mac or `Ctrl+Shift+I` on Windows.
3022 
23**3. Find your app's iframe**
24 
25Inspect the tool call element and look for an iframe nested inside another iframe. Your app is loaded as the content of the inner iframe.
26 
27> [!TIP]
28>
29> From the **Developer** menu, select **Reload MCP Configuration** after editing your `claude_desktop_config.json` to apply changes without restarting.
30 
3131### iOS
3232 
3333On iOS, the Claude app renders your MCP App inside a `WKWebView`. You can inspect it from a connected Mac using Safari's Web Inspector. Follow Apple's guide to [inspecting iOS](https://developer.apple.com/documentation/safari-developer-tools/inspecting-ios) for setup. Once connected, the Claude web view appears under your device in Safari's **Develop** menu, and you can use the console, network panel, and element inspector as you would on desktop.
from line 44
4444 
4545Your app must call [`app.connect()`](https://modelcontextprotocol.github.io/ext-apps/api/classes/app.App.html#connect) in vanilla JS or [`useApp()`](https://modelcontextprotocol.github.io/ext-apps/api/functions/_modelcontextprotocol_ext-apps_react.useApp.html) in React to establish communication with Claude Desktop. Register your handlers before connecting:
4646 
47<CodeGroup>
48 ```javascript Vanilla JS theme={null}
49 import { App } from "@modelcontextprotocol/ext-apps";
47```javascript Vanilla JS theme={null}
48import { App } from "@modelcontextprotocol/ext-apps";
5049 
51 const app = new App({ name: "My App", version: "1.0.0" });
50const app = new App({ name: "My App", version: "1.0.0" });
5251 
53 // Register handlers before connecting
54 app.ontoolresult = (result) => {
55 // Handle tool results
56 };
52// Register handlers before connecting
53app.ontoolresult = (result) => {
54 // Handle tool results
55};
5756 
58 await app.connect();
59 ```
57await app.connect();
58```
6059 
61 ```javascript React theme={null}
62 import { useApp } from "@modelcontextprotocol/ext-apps/react";
60```javascript React theme={null}
61import { useApp } from "@modelcontextprotocol/ext-apps/react";
6362 
64 function MyComponent() {
65 // The useApp hook handles connection automatically
66 const { app } = useApp({
67 appInfo: { name: "My App", version: "1.0.0" },
68 capabilities: {},
69 onAppCreated: (app) => {
70 app.ontoolresult = (result) => {
71 // Handle tool results
72 };
73 }
74 });
75 }
76 ```
77</CodeGroup>
63function MyComponent() {
64 // The useApp hook handles connection automatically
65 const { app } = useApp({
66 appInfo: { name: "My App", version: "1.0.0" },
67 capabilities: {},
68 onAppCreated: (app) => {
69 app.ontoolresult = (result) => {
70 // Handle tool results
71 };
72 }
73 });
74}
75```
7876 
79<Warning>Event handlers like `app.ontoolinput` and `app.ontoolresult` aren't invoked until the app is connected.</Warning>
77> [!WARNING]
78>
79> Event handlers like `app.ontoolinput` and `app.ontoolresult` aren't invoked until the app is connected.
8080 
8181#### Iframe has zero height
8282 
from line 91
9191 
9292When a tool result exceeds approximately 150,000 characters and Claude's code execution sandbox is active, Claude writes the result to the sandbox filesystem instead of passing it inline to the conversation. Your app receives a pointer to the stored file rather than the structured content it needs, so it never hydrates.
9393 
94<Note>This \~150,000-character threshold is specific to claude.ai and Claude Desktop. Claude Code uses a separate 25,000-token default limit, configurable through `MAX_MCP_OUTPUT_TOKENS`.</Note>
94> [!NOTE]
95>
96> This \~150,000-character threshold is specific to claude.ai and Claude Desktop. Claude Code uses a separate 25,000-token default limit, configurable through `MAX_MCP_OUTPUT_TOKENS`.
9597 
9698To stay under the threshold, keep initial tool result payloads lean:
9799 
from line 109
107109 
108110To fix the iOS failures, allowlist on the `Origin` header instead of `Referer`, because WebKit does send `Origin`. Requests from your app carry an `Origin` of `{hash}.claudemcpcontent.com`, and [Set `ui.domain` for Claude](/docs/connectors/building/mcp-apps/getting-started#set-ui-domain-for-claude) shows how to compute the hash for your server URL. Configure your infrastructure to allow requests whose `Origin` matches `*.claudemcpcontent.com` and return a corresponding `Access-Control-Allow-Origin` header.
109111 
110<Note>The missing `Referer` header affects requests your app makes directly from the user's device, such as loading bundles and images or calling your own API from client-side code. MCP tool calls are proxied through Claude's backend and egress from Anthropic's published IP ranges, not the user's device.</Note>
112> [!NOTE]
113>
114> The missing `Referer` header affects requests your app makes directly from the user's device, such as loading bundles and images or calling your own API from client-side code. MCP tool calls are proxied through Claude's backend and egress from Anthropic's published IP ranges, not the user's device.
111115 
112116### `ui.domain` validation fails
113117 

connectors/building/mcpb Changed · +28 / -30 lines

from line 8
88 
99For a directory listing, build a remote MCP server, which reaches people on every surface, or include the local server in a plugin, as [Decide what to include in your plugin](/docs/connectors/building/what-to-build) explains.
1010 
11<Note>
12 * If you're building a remote server, see [Build an MCP server for Claude](/docs/connectors/building/index)
13 * If you're deploying desktop extensions across a Team or Enterprise organization, see [Install a local connector in the desktop app](/docs/connectors/custom/add-unlisted#install-a-local-connector-in-the-desktop-app)
14</Note>
11> [!NOTE]
12>
13> * If you're building a remote server, see [Build an MCP server for Claude](/docs/connectors/building/index)
14> * If you're deploying desktop extensions across a Team or Enterprise organization, see [Install a local connector in the desktop app](/docs/connectors/custom/add-unlisted#install-a-local-connector-in-the-desktop-app)
1515 
1616## Decide when to build an MCPB
1717 
from line 67
6767 
6868The MCPB CLI generates the manifest and packs the bundle.
6969 
70<Steps>
71 <Step title="Install the MCPB CLI">
72 ```bash theme={null}
73 npm install -g @anthropic-ai/mcpb
74 ```
75 </Step>
70**1. Install the MCPB CLI**
7671 
77 <Step title="Create your MCP server">
78 Build a stdio MCP server using the [MCP SDK](https://www.npmjs.com/package/@modelcontextprotocol/sdk).
79 </Step>
72```bash theme={null}
73npm install -g @anthropic-ai/mcpb
74```
8075 
81 <Step title="Generate the manifest">
82 ```bash theme={null}
83 mcpb init
84 ```
85 </Step>
76**2. Create your MCP server**
8677 
87 <Step title="Bundle">
88 ```bash theme={null}
89 mcpb pack
90 ```
91 </Step>
78Build a stdio MCP server using the [MCP SDK](https://www.npmjs.com/package/@modelcontextprotocol/sdk).
9279 
93 <Step title="Install and test in Claude Desktop">
94 Double-click the generated `.mcpb` file.
95 </Step>
96</Steps>
80**3. Generate the manifest**
9781 
82```bash theme={null}
83mcpb init
84```
85 
86**4. Bundle**
87 
88```bash theme={null}
89mcpb pack
90```
91 
92**5. Install and test in Claude Desktop**
93 
94Double-click the generated `.mcpb` file.
95 
9896For detailed implementation guidance, see the [MCPB repository](https://github.com/modelcontextprotocol/mcpb), the [examples directory](https://github.com/modelcontextprotocol/mcpb/tree/main/examples) including a Hello World, and the [README "For Bundle Developers" section](https://github.com/modelcontextprotocol/mcpb/blob/main/README.md).
9997 
100<Warning>
101 Before distributing your MCPB, review the testing and best-practices guidance in the MCPB README to ensure quality.
102</Warning>
98> [!WARNING]
99>
100> Before distributing your MCPB, review the testing and best-practices guidance in the MCPB README to ensure quality.
103101 
104102## Configure manifest.json
105103 

connectors/building/quickstart Changed · +160 / -166 lines

from line 6
66 
77This quickstart is for developers who haven't built an MCP server before and want to see every piece working before they add authentication, hosting, and real tools.
88 
9<Note>
10 * If you already have a server and want to know what Claude's client supports, see [Build an MCP server for Claude](/docs/connectors/building/index)
11 * If your server is running and you want to try it in Claude, see [Test your connector](/docs/connectors/building/testing)
12 * If you want to package skills and an existing connector rather than write a server, see [Build your first plugin](/docs/plugins/quickstart)
13</Note>
9> [!NOTE]
10>
11> * If you already have a server and want to know what Claude's client supports, see [Build an MCP server for Claude](/docs/connectors/building/index)
12> * If your server is running and you want to try it in Claude, see [Test your connector](/docs/connectors/building/testing)
13> * If you want to package skills and an existing connector rather than write a server, see [Build your first plugin](/docs/plugins/quickstart)
1414 
1515## Before you begin
1616 
from line 25
2525 
2626The project is a folder with a `package.json` and two dependencies: the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk), which also works from plain JavaScript, and `zod`, which the SDK uses to describe tool inputs.
2727 
28<Steps>
29 <Step title="Make a folder for the server">
30 In your terminal, create a folder named `mcp-quickstart` and move into it. Every later command on this page runs from this folder:
28**1. Make a folder for the server**
3129 
32 ```bash theme={null}
33 mkdir mcp-quickstart
34 cd mcp-quickstart
35 ```
36 </Step>
30In your terminal, create a folder named `mcp-quickstart` and move into it. Every later command on this page runs from this folder:
3731 
38 <Step title="Create package.json">
39 Create a default `package.json`, then set `"type": "module"` in it so Node.js treats the project's files as ES modules:
32```bash theme={null}
33mkdir mcp-quickstart
34cd mcp-quickstart
35```
4036 
41 ```bash theme={null}
42 npm init -y
43 npm pkg set type=module
44 ```
45 </Step>
37**2. Create package.json**
4638 
47 <Step title="Install the SDK">
48 Install the SDK and `zod`. You don't install a web framework separately, because the SDK depends on Express and gives you a ready-made app for it:
39Create a default `package.json`, then set `"type": "module"` in it so Node.js treats the project's files as ES modules:
4940 
50 ```bash theme={null}
51 npm install @modelcontextprotocol/sdk zod
52 ```
41```bash theme={null}
42npm init -y
43npm pkg set type=module
44```
5345 
54 To confirm what installed, run `npm ls --depth=0`. The output lists the two packages and their versions:
46**3. Install the SDK**
5547 
56 ```text theme={null}
57 [email protected] /path/to/mcp-quickstart
58 +-- @modelcontextprotocol/[email protected]
60 ```
48Install the SDK and `zod`. You don't install a web framework separately, because the SDK depends on Express and gives you a ready-made app for it:
6149 
62 This page was verified with those versions.
63 </Step>
64</Steps>
50```bash theme={null}
51npm install @modelcontextprotocol/sdk zod
52```
6553 
54To confirm what installed, run `npm ls --depth=0`. The output lists the two packages and their versions:
55 
56```text theme={null}
57[email protected] /path/to/mcp-quickstart
58+-- @modelcontextprotocol/[email protected]
60```
61 
62This page was verified with those versions.
63 
6664## Write the server
6765 
6866The whole server is one file. Create `server.mjs` in the `mcp-quickstart` folder and paste in the code below.
from line 129
131129 
132130Before you involve Claude, start the server and send it MCP requests yourself with `curl`, so you know it works on its own.
133131 
134<Steps>
135 <Step title="Start the server">
136 In your first terminal, from the `mcp-quickstart` folder, start the server:
132**1. Start the server**
137133 
138 ```bash theme={null}
139 node server.mjs
140 ```
134In your first terminal, from the `mcp-quickstart` folder, start the server:
141135 
142 It prints the address it's listening on and keeps running:
136```bash theme={null}
137node server.mjs
138```
143139 
144 ```text theme={null}
145 quickstart-server listening on http://localhost:3000/mcp
146 ```
140It prints the address it's listening on and keeps running:
147141 
148 Leave this terminal open. If you see `failed to listen on 3000` instead, another program is using the port. Stop that program and run the command again.
149 </Step>
142```text theme={null}
143quickstart-server listening on http://localhost:3000/mcp
144```
150145 
151 <Step title="List the server's tools">
152 In your second terminal, ask the server what tools it has. This is the same `tools/list` request Claude sends when it connects. The `Accept` header is required, and the server answers `406 Not Acceptable` without it:
146Leave this terminal open. If you see `failed to listen on 3000` instead, another program is using the port. Stop that program and run the command again.
153147 
154 ```bash theme={null}
155 curl -sS -X POST http://localhost:3000/mcp \
156 -H 'Content-Type: application/json' \
157 -H 'Accept: application/json, text/event-stream' \
158 -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
159 ```
148**2. List the server's tools**
160149 
161 The reply is one server-sent event whose `data` line lists `roll_dice` with the title, description, and input schema you wrote. The `inputSchema` object is shortened to `...` here:
150In your second terminal, ask the server what tools it has. This is the same `tools/list` request Claude sends when it connects. The `Accept` header is required, and the server answers `406 Not Acceptable` without it:
162151 
163 ```text theme={null}
164 event: message
165 data: {"result":{"tools":[{"name":"roll_dice","title":"Roll dice","description":"Roll `count` dice with `sides` sides each. Returns each roll and the total.","inputSchema":{...},"execution":{"taskSupport":"forbidden"}}]},"jsonrpc":"2.0","id":2}
166 ```
167 </Step>
152```bash theme={null}
153curl -sS -X POST http://localhost:3000/mcp \
154 -H 'Content-Type: application/json' \
155 -H 'Accept: application/json, text/event-stream' \
156 -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
157```
168158 
169 <Step title="Call the tool">
170 Call `roll_dice` directly with three 20-sided dice, which is the `tools/call` request Claude sends when it uses the tool:
159The reply is one server-sent event whose `data` line lists `roll_dice` with the title, description, and input schema you wrote. The `inputSchema` object is shortened to `...` here:
171160 
172 ```bash theme={null}
173 curl -sS -X POST http://localhost:3000/mcp \
174 -H 'Content-Type: application/json' \
175 -H 'Accept: application/json, text/event-stream' \
176 -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"roll_dice","arguments":{"sides":20,"count":3}}}'
177 ```
161```text theme={null}
162event: message
163data: {"result":{"tools":[{"name":"roll_dice","title":"Roll dice","description":"Roll `count` dice with `sides` sides each. Returns each roll and the total.","inputSchema":{...},"execution":{"taskSupport":"forbidden"}}]},"jsonrpc":"2.0","id":2}
164```
178165 
179 The `data` line carries the text your handler returned, with your own random rolls:
166**3. Call the tool**
180167 
181 ```text theme={null}
182 event: message
183 data: {"result":{"content":[{"type":"text","text":"Rolled 3d20: [16, 10, 20] total=46"}]},"jsonrpc":"2.0","id":3}
184 ```
185 </Step>
186</Steps>
168Call `roll_dice` directly with three 20-sided dice, which is the `tools/call` request Claude sends when it uses the tool:
187169 
170```bash theme={null}
171curl -sS -X POST http://localhost:3000/mcp \
172 -H 'Content-Type: application/json' \
173 -H 'Accept: application/json, text/event-stream' \
174 -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"roll_dice","arguments":{"sides":20,"count":3}}}'
175```
176 
177The `data` line carries the text your handler returned, with your own random rolls:
178 
179```text theme={null}
180event: message
181data: {"result":{"content":[{"type":"text","text":"Rolled 3d20: [16, 10, 20] total=46"}]},"jsonrpc":"2.0","id":3}
182```
183 
188184## Connect the server to Claude Code
189185 
190186With the server answering on its own, register it with Claude Code and have Claude call the tool from a prompt. Run these commands in your second terminal from the `mcp-quickstart` folder, because `claude mcp add` saves the server for the current project folder by default, and Claude Code only sees it when you run from that same folder.
191187 
192<Steps>
193 <Step title="Add the server to Claude Code">
194 Register the server under the name `quickstart`, with the HTTP transport and the local URL:
188**1. Add the server to Claude Code**
195189 
196 ```bash theme={null}
197 claude mcp add --transport http quickstart http://localhost:3000/mcp
198 ```
190Register the server under the name `quickstart`, with the HTTP transport and the local URL:
199191 
200 Claude Code confirms where it saved the entry:
192```bash theme={null}
193claude mcp add --transport http quickstart http://localhost:3000/mcp
194```
201195 
202 ```text theme={null}
203 Added HTTP MCP server quickstart with URL: http://localhost:3000/mcp to local config
204 ```
205 </Step>
196Claude Code confirms where it saved the entry:
206197 
207 <Step title="Check the connection">
208 List your servers. Claude Code connects to each one to check its health:
198```text theme={null}
199Added HTTP MCP server quickstart with URL: http://localhost:3000/mcp to local config
200```
209201 
210 ```bash theme={null}
211 claude mcp list
212 ```
202**2. Check the connection**
213203 
214 A working server shows as connected:
204List your servers. Claude Code connects to each one to check its health:
215205 
216 ```text theme={null}
217 Checking MCP server health…
206```bash theme={null}
207claude mcp list
208```
218209 
219 quickstart: http://localhost:3000/mcp (HTTP) - ✔ Connected
220 ```
210A working server shows as connected:
221211 
222 If it shows an error instead, check that `node server.mjs` is still running in the first terminal.
223 </Step>
212```text theme={null}
213Checking MCP server health…
224214 
225 <Step title="Ask Claude to roll dice">
226 Send Claude one prompt with [`claude -p`](https://code.claude.com/docs/en/headless), which runs a single prompt without opening an interactive session and prints the answer. Claude Code names MCP tools `mcp__<server>__<tool>`, so your tool is `mcp__quickstart__roll_dice`, and `--allowedTools` lets Claude call it without stopping to ask you:
215quickstart: http://localhost:3000/mcp (HTTP) - ✔ Connected
216```
227217 
228 ```bash theme={null}
229 claude -p "Roll three 20-sided dice using the quickstart server and tell me each roll and the total." \
230 --allowedTools "mcp__quickstart__roll_dice"
231 ```
218If it shows an error instead, check that `node server.mjs` is still running in the first terminal.
232219 
233 Claude calls your server and reports the rolls it got back:
220**3. Ask Claude to roll dice**
234221 
235 ```text theme={null}
236 Rolled 3d20 via the quickstart server:
222Send Claude one prompt with [`claude -p`](https://code.claude.com/docs/en/headless), which runs a single prompt without opening an interactive session and prints the answer. Claude Code names MCP tools `mcp__<server>__<tool>`, so your tool is `mcp__quickstart__roll_dice`, and `--allowedTools` lets Claude call it without stopping to ask you:
237223 
238 - Roll 1: **12**
239 - Roll 2: **4**
240 - Roll 3: **5**
224```bash theme={null}
225claude -p "Roll three 20-sided dice using the quickstart server and tell me each roll and the total." \
226 --allowedTools "mcp__quickstart__roll_dice"
227```
241228 
242 **Total: 21**
243 ```
244 </Step>
229Claude calls your server and reports the rolls it got back:
245230 
246 <Step title="Confirm the tool ran">
247 To see the tool call itself rather than Claude's summary of it, run the same prompt with streaming JSON output. `--output-format stream-json` needs `--verbose` alongside it:
231```text theme={null}
232Rolled 3d20 via the quickstart server:
248233 
249 ```bash theme={null}
250 claude -p "Roll three 20-sided dice using the quickstart server and tell me each roll and the total." \
251 --allowedTools "mcp__quickstart__roll_dice" \
252 --output-format stream-json --verbose
253 ```
234- Roll 1: **12**
235- Roll 2: **4**
236- Roll 3: **5**
254237 
255 The output is one JSON object per line, and other tool calls can appear before yours. Look for the `tool_use` block whose `name` is `mcp__quickstart__roll_dice`, carrying the arguments Claude chose, and the `tool_result` block after it carrying your server's text, shown here with the surrounding fields removed:
238**Total: 21**
239```
256240 
257 ```text theme={null}
258 {"type":"tool_use","name":"mcp__quickstart__roll_dice","input":{"count":3,"sides":20}, ...}
259 {"type":"tool_result","content":[{"type":"text","text":"Rolled 3d20: [9, 14, 5] total=28"}], ...}
260 ```
261 </Step>
262</Steps>
241**4. Confirm the tool ran**
242 
243To see the tool call itself rather than Claude's summary of it, run the same prompt with streaming JSON output. `--output-format stream-json` needs `--verbose` alongside it:
244 
245```bash theme={null}
246claude -p "Roll three 20-sided dice using the quickstart server and tell me each roll and the total." \
247 --allowedTools "mcp__quickstart__roll_dice" \
248 --output-format stream-json --verbose
249```
250 
251The output is one JSON object per line, and other tool calls can appear before yours. Look for the `tool_use` block whose `name` is `mcp__quickstart__roll_dice`, carrying the arguments Claude chose, and the `tool_result` block after it carrying your server's text, shown here with the surrounding fields removed:
252 
253```text theme={null}
254{"type":"tool_use","name":"mcp__quickstart__roll_dice","input":{"count":3,"sides":20}, ...}
255{"type":"tool_result","content":[{"type":"text","text":"Rolled 3d20: [9, 14, 5] total=28"}], ...}
256```
263257 
264258In an interactive `claude` session you can ask the same thing in your own words. Without `--allowedTools`, Claude Code asks for your permission before it calls `roll_dice`.
265259 

connectors/building/review-criteria Changed · +3 / -3 lines

from line 4
44 
55This checklist covers what Anthropic checks on an MCP connector submitted to the directory, organized around the most common rejection reasons so you can correct them before you submit. It's for connector authors preparing a [directory submission](/docs/connectors/building/submission). For the full legal text behind these criteria, see the [Software Directory Policy](https://support.claude.com/en/articles/13145358-anthropic-software-directory-policy).
66 
7<Note>
8 If you're submitting a plugin, see the [plugin pre-submission checklist](/docs/plugins/pre-submission-checklist) instead.
9</Note>
7> [!NOTE]
8>
9> If you're submitting a plugin, see the [plugin pre-submission checklist](/docs/plugins/pre-submission-checklist) instead.
1010 
1111Work through [tool design](#design-tools-that-pass-review), [server behavior](#server-behavior-and-scope), and [what to include with your submission](#gather-what-to-include-with-your-submission), then [test every tool](#test-before-you-submit) before you open the portal. [How Anthropic reviews connectors](#understand-how-anthropic-reviews-connectors) covers the Community and Verified labels.
1212 

connectors/building/submission Changed · +37 / -39 lines

from line 6
66 
77This page is for connector authors who are ready to submit. It starts with a [checklist](#pre-submission-checklist-for-connectors), then covers [where to submit your connector](#choose-where-to-submit-your-connector), the [requirements every submission must meet](#meet-the-submission-requirements), and [what to have ready for each step of the portal](#submit-through-the-developer-portal). [Connector review criteria](/docs/connectors/building/review-criteria) explains what reviewers look for in your tools and server, so read it alongside the checklist.
88 
9<Note>
10 If you're submitting a plugin rather than a connector, see [Submit a plugin](/docs/plugins/submit) instead.
11</Note>
9> [!NOTE]
10>
11> If you're submitting a plugin rather than a connector, see [Submit a plugin](/docs/plugins/submit) instead.
1212 
1313## Pre-submission checklist for connectors
1414 
from line 82
8282* Data retention
8383* Contact information
8484 
85<Warning>
86 Missing or incomplete privacy policies result in immediate rejection.
87</Warning>
85> [!WARNING]
86>
87> Missing or incomplete privacy policies result in immediate rejection.
8888 
8989### Allowed link URIs
9090 
from line 129
129129 
130130Your progress saves automatically in your browser as you move between steps, so within a browser session you can return to earlier steps without losing work. Each step asks for the following:
131131 
132<Steps>
133 <Step title="Connection">
134 Connect the server you're submitting, by pasting its `https://` URL or choosing a custom connector you've already added to Claude. If your users connect to different URLs, select **Users connect to different URLs** and choose **Multiple URLs** or **URL pattern**.
135 </Step>
132**1. Connection**
136133 
137 <Step title="Tools">
138 Your server's tools, prompts, and resources sync automatically from the connected server, grouped by whether their annotations declare them read-only or write. If any tools are flagged for missing titles or annotations, fix them on your server before submitting.
139 </Step>
134Connect the server you're submitting, by pasting its `https://` URL or choosing a custom connector you've already added to Claude. If your users connect to different URLs, select **Users connect to different URLs** and choose **Multiple URLs** or **URL pattern**.
140135 
141 <Step title="Listing">
142 The public-facing listing: server name up to 100 characters, one-liner up to 200 characters, description up to 2,000 characters, one to five categories, documentation URL, privacy policy URL, support contact, icon, and the URL slug for your listing page. The slug is permanent once published.
143 </Step>
136**2. Tools**
144137 
145 <Step title="Use cases">
146 The primary use cases, what users need before they can connect, such as accounts, plans, or other setup, and whether the connector reads data, writes data, or both.
147 </Step>
138Your server's tools, prompts, and resources sync automatically from the connected server, grouped by whether their annotations declare them read-only or write. If any tools are flagged for missing titles or annotations, fix them on your server before submitting.
148139 
149 <Step title="Company">
150 Company name and website, plus a primary contact for review updates.
151 </Step>
140**3. Listing**
152141 
153 <Step title="Authentication">
154 How users authenticate: OAuth with dynamic client registration, client ID metadata documents, or Anthropic-held client credentials; a custom connection where users supply their own URL or credentials at connection time; or no authentication. [Authentication](/docs/connectors/building/authentication) covers which modes are supported directly and which need coordination with the review team. If your server starts without authentication and individual tools prompt for it on demand, you can flag that here.
142The public-facing listing: server name up to 100 characters, one-liner up to 200 characters, description up to 2,000 characters, one to five categories, documentation URL, privacy policy URL, support contact, icon, and the URL slug for your listing page. The slug is permanent once published.
155143 
156 If you chose **URL pattern** in the Connection step, Anthropic-held client credentials can't be used. If you chose **Multiple URLs**, a custom connection can't be used.
157 </Step>
144**4. Use cases**
158145 
159 <Step title="Data handling">
160 Whether the underlying API is your own, proxied from a partner with permission, or a third party's you don't control, and whether the connector handles personal health data or sponsored content.
161 </Step>
146The primary use cases, what users need before they can connect, such as accounts, plans, or other setup, and whether the connector reads data, writes data, or both.
162147 
163 <Step title="Test & launch">
164 Test-account setup and access instructions detailed enough for a reviewer to connect to your server and run its tools: every link, credential, and step, including credentials for a fully populated account where relevant. You also confirm you've run every tool yourself, either through MCP Inspector or as a custom connector in Claude.
165 </Step>
148**5. Company**
166149 
167 <Step title="Compliance">
168 Seven policy acknowledgments covering the directory guidelines, first-party API usage, financial transactions, AI media generation, prompt injection, conversation data collection, and public documentation. All seven are required.
169 </Step>
150Company name and website, plus a primary contact for review updates.
170151 
171 <Step title="Review and submit">
172 Everything you've entered, for a final check before you submit. Any quality warnings, such as very short answers, appear here and are shared with the review team alongside your submission.
173 </Step>
174</Steps>
152**6. Authentication**
153 
154How users authenticate: OAuth with dynamic client registration, client ID metadata documents, or Anthropic-held client credentials; a custom connection where users supply their own URL or credentials at connection time; or no authentication. [Authentication](/docs/connectors/building/authentication) covers which modes are supported directly and which need coordination with the review team. If your server starts without authentication and individual tools prompt for it on demand, you can flag that here.
155 
156If you chose **URL pattern** in the Connection step, Anthropic-held client credentials can't be used. If you chose **Multiple URLs**, a custom connection can't be used.
157 
158**7. Data handling**
159 
160Whether the underlying API is your own, proxied from a partner with permission, or a third party's you don't control, and whether the connector handles personal health data or sponsored content.
161 
162**8. Test & launch**
163 
164Test-account setup and access instructions detailed enough for a reviewer to connect to your server and run its tools: every link, credential, and step, including credentials for a fully populated account where relevant. You also confirm you've run every tool yourself, either through MCP Inspector or as a custom connector in Claude.
165 
166**9. Compliance**
167 
168Seven policy acknowledgments covering the directory guidelines, first-party API usage, financial transactions, AI media generation, prompt injection, conversation data collection, and public documentation. All seven are required.
169 
170**10. Review and submit**
171 
172Everything you've entered, for a final check before you submit. Any quality warnings, such as very short answers, appear here and are shared with the review team alongside your submission.
175173 
176174## After you submit
177175 

connectors/building/testing Changed · +3 / -3 lines

from line 26
2626const app = createMcpExpressApp({ allowedHosts: ['localhost', '127.0.0.1', 'abc123.example.com'] });
2727```
2828 
29<Warning>
30 A tunnel exposes your local server to the public internet. Keep authentication enabled on your server while tunneling, and shut the tunnel down when you're done testing.
31</Warning>
29> [!WARNING]
30>
31> A tunnel exposes your local server to the public internet. Keep authentication enabled on your server while tunneling, and shut the tunnel down when you're done testing.
3232 
3333### Validate with MCP Inspector
3434 
Feedback