Skip to content

Commit 00e0ed4

Browse files
Fix bullet point and list item style throughout
1 parent 8f70fd8 commit 00e0ed4

File tree

1 file changed

+53
-53
lines changed

1 file changed

+53
-53
lines changed

astro/src/content/docs/extend/examples/protecting-mcp-servers.mdx

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,20 @@ Building a production Model Context Protocol (MCP) server requires you to consid
1515

1616
Starting with a simple, unprotected MCP server and adding OAuth protection step by step, this guide demonstrates how to:
1717

18-
* Examine a basic MCP server with a simple `get_name` tool
19-
* Add FusionAuth OAuth configuration and token validation
20-
* Register an MCP client (such as Claude Desktop) and test the protected tool
18+
* Examine a basic MCP server with a simple `get_name` tool.
19+
* Add FusionAuth OAuth configuration and token validation.
20+
* Register an MCP client (such as Claude Desktop) and test the protected tool.
2121

2222
The following tutorial builds a working example of an OAuth-protected MCP server that you can extend with additional tools and security policies.
2323

2424
## Prerequisites
2525

2626
To follow this guide, you need the following installed:
2727

28-
* Docker Desktop (ensure it's running)
29-
* An MCP client such as Claude Desktop or Cursor
30-
* Python 3.12 or later (and `pip`)
31-
* Node.js version 20.18.1 or later (ensure it's available in your PATH)
28+
* Docker Desktop (ensure it's running).
29+
* An MCP client such as Claude Desktop or Cursor.
30+
* Python 3.12 or later (and `pip`).
31+
* Node.js version 20.18.1 or later (ensure it's available in your PATH).
3232

3333
<Aside type="tip" nodark="true">
3434
Verify your Node.js installation:
@@ -133,9 +133,9 @@ if __name__ == "__main__":
133133

134134
This is a minimal MCP server with:
135135

136-
* A single `get_name` tool that returns a hardcoded greeting
137-
* HTTP transport on port 8000
138-
* No authentication
136+
* A single `get_name` tool that returns a hardcoded greeting.
137+
* HTTP transport on port 8000.
138+
* No authentication.
139139

140140
### Test The Unprotected Server
141141

@@ -147,9 +147,9 @@ docker compose up -d
147147

148148
Wait about 20 seconds for all services to start, including:
149149

150-
* **FusionAuth:** On port 9011, preconfigured via Kickstart
151-
* **MCP Server:** The unprotected starter version on port 8000
152-
* **PostgreSQL:** The FusionAuth database
150+
* **FusionAuth:** On port 9011, preconfigured via Kickstart.
151+
* **MCP Server:** The unprotected starter version on port 8000.
152+
* **PostgreSQL:** The FusionAuth database.
153153

154154
Check that all services are healthy:
155155

@@ -252,11 +252,11 @@ In this code:
252252

253253
The values of the configuration variables match the Kickstart configuration in `kickstart/kickstart.json`. When you ran `docker compose up`, FusionAuth detected a fresh installation and automatically ran the Kickstart configuration, which created:
254254

255-
* An OAuth application called "MCP Server"
256-
* A test user (`test@example.com`/`password`)
257-
* An admin user (`admin@example.com`/`password`) with access to the FusionAuth admin UI
258-
* An API key
259-
* A custom `get_name` scope
255+
* An OAuth application called "MCP Server".
256+
* A test user (`test@example.com`/`password`).
257+
* An admin user (`admin@example.com`/`password`) with access to the FusionAuth admin UI.
258+
* An API key.
259+
* A custom `get_name` scope.
260260

261261
The Ids and keys are hardcoded in the Kickstart file. In production, you'd use environment variables or secrets management instead of hardcoded values.
262262

@@ -324,10 +324,10 @@ class FusionAuthTokenVerifier(TokenVerifier):
324324

325325
This token verifier validates access tokens by:
326326

327-
1. Calling FusionAuth's UserInfo endpoint with the token. A `200 OK` response confirms the token is valid
328-
2. Decoding the JWT to extract scopes, since the UserInfo endpoint does not return them. FastMCP needs scopes to enforce per-tool access control
329-
3. Merging the UserInfo response into the token claims, so tools can read profile data (name, email) without making a separate API call
330-
4. Returning an `AccessToken` object that FastMCP can use
327+
1. Calling FusionAuth's UserInfo endpoint with the token. A `200 OK` response confirms the token is valid.
328+
2. Decoding the JWT to extract scopes, since the UserInfo endpoint does not return them. FastMCP needs scopes to enforce per-tool access control.
329+
3. Merging the UserInfo response into the token claims, so tools can read profile data (name, email) without making a separate API call.
330+
4. Returning an `AccessToken` object that FastMCP can use.
331331

332332
### Step 3: Enable OAuth On The MCP Server
333333

@@ -393,10 +393,10 @@ def get_name() -> str:
393393

394394
This code updates the `get_name` tool so that it:
395395

396-
1. Uses the FastMCP `get_access_token()` to get the validated access token from the request context
397-
2. Reads `given_name` and `family_name` directly from the token claims, which were populated from the UserInfo response in `verify_token`, so no additional API call is needed
398-
3. Falls back to `preferred_username`, then `email`, then the user Id if no name is available
399-
4. Returns a personalized greeting
396+
1. Uses the FastMCP `get_access_token()` to get the validated access token from the request context.
397+
2. Reads `given_name` and `family_name` directly from the token claims, which were populated from the UserInfo response in `verify_token`, so no additional API call is needed.
398+
3. Falls back to `preferred_username`, then `email`, then the user Id if no name is available.
399+
4. Returns a personalized greeting.
400400

401401
### Step 5: Rebuild And Restart
402402

@@ -459,9 +459,9 @@ Each MCP client needs its own dedicated OAuth application in FusionAuth, separat
459459

460460
For the OAuth grant to work, FusionAuth needs to know:
461461

462-
* Which redirect URLs are allowed for each MCP client
463-
* Whether PKCE is required (some MCP clients use PKCE for security)
464-
* Which OAuth grants are enabled
462+
* Which redirect URLs are allowed for each MCP client.
463+
* Whether PKCE is required (some MCP clients use PKCE for security).
464+
* Which OAuth grants are enabled.
465465

466466
### What The Setup Script Does
467467

@@ -512,11 +512,11 @@ def create_client_application(base_url: str, api_key: str, client_name: str):
512512

513513
This includes the following key configuration values:
514514

515-
* `authorizedURLValidationPolicy: "AllowWildcards"` enables wildcard matching for redirect URLs, so the OAuth callback works regardless of which port the MCP client picks
516-
* `proofKeyForCodeExchangePolicy: "Required"` enforces PKCE for security
517-
* `clientAuthenticationPolicy: "NotRequiredWhenUsingPKCE"` means that no client secret is needed when PKCE is used
518-
* `scopeHandlingPolicy: "Compatibility"` allows custom scopes like `get_name`
519-
* `unknownScopePolicy: "Allow"` permits scopes that aren't pre-defined in FusionAuth
515+
* `authorizedURLValidationPolicy: "AllowWildcards"` enables wildcard matching for redirect URLs, so the OAuth callback works regardless of which port the MCP client picks.
516+
* `proofKeyForCodeExchangePolicy: "Required"` enforces PKCE for security.
517+
* `clientAuthenticationPolicy: "NotRequiredWhenUsingPKCE"` means that no client secret is needed when PKCE is used.
518+
* `scopeHandlingPolicy: "Compatibility"` allows custom scopes like `get_name`.
519+
* `unknownScopePolicy: "Allow"` permits scopes that aren't pre-defined in FusionAuth.
520520

521521
The script returns the generated client Id, which you need for configuring your MCP client.
522522

@@ -604,9 +604,9 @@ Restart your MCP client to load the new configuration.
604604

605605
When your MCP client starts, it does the following:
606606

607-
1. Discovers the OAuth configuration from the MCP server
608-
2. Opens your browser to FusionAuth's login page
609-
3. Asks you to authenticate and grant consent
607+
1. Discovers the OAuth configuration from the MCP server.
608+
2. Opens your browser to FusionAuth's login page.
609+
3. Asks you to authenticate and grant consent.
610610

611611
<Aside type="note" nodark="true">
612612
If the OAuth flow doesn't start or you encounter errors, consult the [Troubleshooting section](#troubleshooting) below.
@@ -666,28 +666,28 @@ When an MCP client connects to the MCP server, here's what happens:
666666

667667
The MCP server validates tokens using the FusionAuth UserInfo endpoint. This is a standard OpenID Connect endpoint that:
668668

669-
* Accepts `Bearer` tokens in the `Authorization` header
670-
* Returns user information if the token is valid
671-
* Does not require client authentication (because the token authenticates the request)
669+
* Accepts `Bearer` tokens in the `Authorization` header.
670+
* Returns user information if the token is valid.
671+
* Does not require client authentication (because the token authenticates the request).
672672

673673
If the UserInfo endpoint returns `200 OK`, the token is valid and active.
674674

675675
### Scope Enforcement
676676

677677
The `required_scopes=["openid", "profile", "email", "get_name"]` parameter ensures that:
678678

679-
* Only tokens containing all four scopes can execute tools
680-
* FastMCP automatically validates scopes before tool execution
681-
* Users must explicitly grant these scopes during the consent flow
682-
* The `openid`, `profile`, and `email` scopes cause FusionAuth to include user profile data in the UserInfo response, so the MCP server can read the user's name without a separate API call
679+
* Only tokens containing all four scopes can execute tools.
680+
* FastMCP automatically validates scopes before tool execution.
681+
* Users must explicitly grant these scopes during the consent flow.
682+
* The `openid`, `profile`, and `email` scopes cause FusionAuth to include user profile data in the UserInfo response, so the MCP server can read the user's name without a separate API call.
683683

684684
### Custom Scopes In FusionAuth
685685

686686
FusionAuth requires an Essentials license (or higher) to use custom scopes. The Kickstart configuration:
687687

688-
1. Activates the license
689-
2. Creates the `get_name` scope
690-
3. Configures the application with the scope
688+
1. Activates the license.
689+
2. Creates the `get_name` scope.
690+
3. Configures the application with the scope.
691691

692692
When users authorize the application, FusionAuth redirects them back to the MCP client with an "Authorization successful!" message.
693693

@@ -712,9 +712,9 @@ For a production deployment, you would host the MCP server on a remote machine a
712712

713713
For a remote deployment you need:
714714

715-
* A server hosting your MCP server, accessible over the internet
716-
* A reverse proxy such as [Caddy](https://caddyserver.com/) (which handles TLS certificates automatically) or TLS configured directly on your load balancer to serve HTTPS on the MCP server endpoint
717-
* A publicly accessible FusionAuth instance, since the OAuth browser redirect requires the user's browser to reach FusionAuth
715+
* A server hosting your MCP server, accessible over the internet.
716+
* A reverse proxy such as [Caddy](https://caddyserver.com/) (which handles TLS certificates automatically) or TLS configured directly on your load balancer to serve HTTPS on the MCP server endpoint.
717+
* A publicly accessible FusionAuth instance, since the OAuth browser redirect requires the user's browser to reach FusionAuth.
718718

719719
### Server Configuration
720720

@@ -866,8 +866,8 @@ When you see this error, look at the error message to find which URL was attempt
866866

867867
You now have a working OAuth-protected MCP server, which you can extend by:
868868

869-
* Adding more tools with different scope requirements
870-
* Implementing role-based access control using FusionAuth roles
871-
* Integrating additional FusionAuth features like multi-factor authentication
869+
* Adding more tools with different scope requirements.
870+
* Implementing role-based access control using FusionAuth roles.
871+
* Integrating additional FusionAuth features like multi-factor authentication.
872872

873873
The token validation and scope enforcement patterns demonstrated here also apply to OAuth-protected REST APIs, GraphQL servers, and other authenticated services.

0 commit comments

Comments
 (0)