Skip to content

Commit f130bbd

Browse files
committed
add instructions for 1 and 2
1 parent b366478 commit f130bbd

File tree

13 files changed

+287
-3
lines changed

13 files changed

+287
-3
lines changed

β€Ž.gitignoreβ€Ž

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ data.db
1212
# in a real app you'd want to not commit the .env
1313
# file as well, but since this is for a workshop
1414
# we're going to keep them around.
15-
# .env
15+
!**/.env
16+
17+
/.env
1618
**/dist/**
1719
**/.wrangler/**
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# CORS
22

3-
πŸ‘¨β€πŸ’Ό Excellent work! We've successfully solved a critical problem for our MCP server users: now they can connect to our server from any domain without encountering frustrating CORS policy errors.
3+
πŸ‘¨β€πŸ’Ό Excellent work! Now our users can connect to our server from any domain without encountering frustrating CORS policy errors.
44

55
This improvement means users get seamless access to our MCP server's capabilities regardless of where they're connecting from. Whether they're using VS Code, a web application, or any other MCP client, the connection just works. This is exactly what users expect from a modern, accessible MCP server.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
# Auth Server Metadata
2+
3+
πŸ‘¨β€πŸ’Ό Fantastic progress! Now, clients can seamlessly discover how to authenticate with our system by accessing the standardized `/.well-known/oauth-authorization-server` endpoint. This means any MCP-compliant client can automatically find out where to register, authorize, and obtain tokensβ€”removing guesswork and enabling smooth, standards-based integration.
4+
5+
By relaying the OAuth server's metadata, we've made our MCP server a true authentication discovery hub. This empowers users to connect their tools and applications with confidence, knowing they have all the information needed for secure authentication.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,64 @@
11
# Protected Resource
2+
3+
πŸ‘¨β€πŸ’Ό For our journaling app it's crucial that clients can discover how to interact with our resource server in a standards-compliant way. The Model Context Protocol (MCP) and OAuth require that we expose a public metadata endpoint so that any client can learn how to authenticate and what endpoints are available. This endpoint must be accessible to everyone, without requiring authentication, because it's the first step in the OAuth discovery process.
4+
5+
But not all protected resources are the same! In a large system, you might have multiple APIs or services, each with its own protected dataβ€”think: user profiles, journal entries, or analytics. Each of these can be considered a separate "protected resource" in OAuth terms. To support this, the OAuth and MCP specifications allow for multiple resource metadata endpoints, each describing a different protected resource.
6+
7+
That's why, for EpicMe, our endpoint is:
8+
9+
```
10+
/.well-known/oauth-protected-resource/mcp
11+
```
12+
13+
The `/mcp` at the end uniquely identifies our journaling MCP endpoint as a specific protected resource. This makes it possible for clients to discover metadata for just the resource they want to access, even if the server hosts several different APIs. It also helps with future extensibilityβ€”if we add more protected resources later, each can have its own metadata endpoint under `/.well-known/oauth-protected-resource/`.
14+
15+
Here's how a client might discover resource server's metadata:
16+
17+
```ts
18+
// Example: Discovering the MCP resource server's metadata
19+
const response = await fetch(
20+
'https://api.example.com/.well-known/oauth-protected-resource/mcp',
21+
)
22+
if (response.ok) {
23+
const metadata = await response.json()
24+
// metadata.authorization_servers tells the client where to go next
25+
} else {
26+
throw new Error('Could not discover resource server metadata.')
27+
}
28+
```
29+
30+
Here's an example of what the metadata content looks like:
31+
32+
```json
33+
{
34+
"resource": "https://api.example.com/mcp",
35+
"authorization_servers": ["https://auth.example.com"]
36+
}
37+
```
38+
39+
There are [a some other fields you can supply](https://datatracker.ietf.org/doc/html/rfc9728#section-3.1), but these are the two most important for our purposes.
40+
41+
Here's the overall flow for a client discovering the resource server and then the authorization server:
42+
43+
```mermaid
44+
sequenceDiagram
45+
Client->>EpicMe: GET /.well-known/oauth-protected-resource/mcp
46+
EpicMe-->>Client: Resource metadata (includes authorization_servers)
47+
Client->>Auth Server: GET /.well-known/oauth-authorization-server
48+
Auth Server-->>Client: Authorization server metadata
49+
```
50+
51+
<callout-info>
52+
🚦 The `/.well-known/oauth-protected-resource/mcp` endpoint must be accessible
53+
to all clients, without authentication. This is required by the OAuth and MCP
54+
specifications.
55+
</callout-info>
56+
57+
<callout-muted>
58+
πŸ“œ For more details, see the [OAuth 2.0 Protected Resource Metadata
59+
RFC](https://datatracker.ietf.org/doc/html/rfc9728).
60+
</callout-muted>
61+
62+
The goal is to make EpicMe's resource server discoverable and easy to integrate with any standards-compliant client. Make sure your implementation exposes the correct metadata endpoint for the MCP resource, so clients can start the OAuth flow without any roadblocks.
63+
64+
Now, check that your `/.well-known/oauth-protected-resource/mcp` endpoint is public, returns the correct metadata, and helps clients discover how to connect to EpicMe securely!
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
# Protected Resource
2+
3+
πŸ‘¨β€πŸ’Ό Outstanding achievement! Now, any client can discover how to interact with our journaling resource server in a standards-compliant way. By exposing the public `/.well-known/oauth-protected-resource/mcp` metadata endpoint, we've made it possible for clients to programmatically learn how to authenticate and which authorization servers to useβ€”without any guesswork or manual configuration.
4+
5+
This means that as our system grows, each protected resource (like user profiles, journal entries, or analytics) can have its own discoverable endpoint, making integration seamless for all clients. Users benefit from a smoother onboarding experience, and developers can confidently build on top of our platform knowing that discovery is simple and future-proof.
6+
7+
But we're just focused on MCP. Let's keep going!
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,17 @@
11
# Metadata Discovery
2+
3+
Congratulations! You've completed the first step in making your MCP server discoverable and accessible to any standards-compliant client.
4+
5+
In this exercise, you:
6+
7+
- Implemented CORS support for your MCP server, ensuring that clients from any domain (including web apps and desktop tools like VS Code) can connect without running into CORS policy errors.
8+
- Used the provided `withCors` utility to handle the complexity of CORS headers, preflight requests, and response modification, so your business logic stays clean and focused.
9+
- Configured your server to expose the correct CORS headers for the `/.well-known` endpoints, which are essential for service discovery and integration.
10+
11+
<callout-success>
12+
By supporting CORS and the discovery endpoints, you've made it possible to use
13+
OAuth to authorize your MCP server to access user data securely and
14+
seamlessly.
15+
</callout-success>
16+
17+
Let's keep building on this foundation to make your MCP server even more powerful and user-friendly!
Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,72 @@
11
# Metadata Discovery
22

3-
NOTE: Make sure to talk about the relationship between auth server and resource server.
3+
Modern applications need a way for clients to discover how to authenticate and interact with servers securely. In the Model Context Protocol (MCP), this is achieved through standardized metadata endpoints that describe both the resource server (your MCP server) and its associated authorization server.
4+
5+
Without this discovery mechanism, clients would have to guess or hardcode authentication details, leading to brittle integrations and poor user experience.
6+
7+
## Why does this matter?
8+
9+
- **Seamless integration:** Clients (like VS Code, web apps, or other tools) can automatically discover how to authenticate and what endpoints to use.
10+
- **Security:** By following standards, you reduce the risk of misconfiguration and security vulnerabilities.
11+
- **Interoperability:** Any MCP-compliant client can connect to any MCP server, regardless of who built it.
12+
13+
## The relationship: Auth Server vs. Resource Server
14+
15+
- **Resource Server:** Your MCP server, which hosts protected resources and APIs.
16+
- **Authorization Server:** Issues access tokens and handles user authentication. It may be a separate service or co-located with the resource server.
17+
18+
Clients first discover the resource server’s metadata, which points them to the authorization server’s metadata. This chain of discovery is what enables secure, standards-based authentication.
19+
20+
<callout-info>
21+
πŸ“œ See the [MCP Authorization
22+
Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)
23+
for details on discovery and metadata.
24+
</callout-info>
25+
26+
## Example: Metadata Discovery Flow
27+
28+
```mermaid
29+
sequenceDiagram
30+
Client->>MCP Server: GET /.well-known/oauth-protected-resource
31+
MCP Server-->>Client: Resource metadata (includes authorization_servers)
32+
Client->>Authorization Server: GET /.well-known/oauth-authorization-server
33+
Authorization Server-->>Client: Authorization server metadata
34+
Client->>Authorization Server: OAuth flow (get token)
35+
Client->>MCP Server: Authenticated request with token
36+
MCP Server-->>Client: Protected resource
37+
```
38+
39+
### Realistic Example
40+
41+
```ts
42+
// Discover resource server metadata
43+
const resourceMeta = await fetch(
44+
'https://our-mcp-server.com/.well-known/oauth-protected-resource',
45+
).then((r) => r.json())
46+
47+
// Find the authorization server URL
48+
const authServerUrl = resourceMeta.authorization_servers[0]
49+
50+
// Discover authorization server metadata
51+
const authMeta = await fetch(authServerUrl).then((r) => r.json())
52+
53+
// Now the client knows how to authenticate!
54+
```
55+
56+
<callout-success>
57+
By implementing these endpoints, you make your MCP server discoverable and
58+
easy to integrate with any standards-compliant client.
59+
</callout-success>
60+
61+
## Recommended Practices
62+
63+
- Always implement both resource and authorization server metadata endpoints.
64+
- Use CORS headers to allow cross-origin discovery.
65+
- Validate and document your endpoints for client developers.
66+
67+
<callout-muted>
68+
πŸ“œ For more, see [RFC 8414: OAuth 2.0 Authorization Server
69+
Metadata](https://datatracker.ietf.org/doc/html/rfc8414) and [RFC 9728: OAuth
70+
2.0 Protected Resource
71+
Metadata](https://datatracker.ietf.org/doc/html/rfc9728).
72+
</callout-muted>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,31 @@
11
# Authenticate Header
2+
3+
πŸ‘¨β€πŸ’Ό In EpicMe, the `Authorization` header is the gatekeeper for every journal entry. Its job is simple but critical: make sure that only requests with valid credentials can access or change journal data. If a request doesn't include this header, it shouldn't get throughβ€”no exceptions. Once the client has an auth token, it'll send that token in the `Authorization` header. If that header doesn't exist, then we know they don't have a token and shouldn't be able to access our server.
4+
5+
But we can help them out by telling them what they need to do to get access. This is where the `WWW-Authenticate` header comes in. It tells the client what kind of authentication is required.
6+
7+
For example, if someone tries to fetch `/api/secret-sandwich-recipes` without authenticating, the server should respond with a clear message and a `WWW-Authenticate` header:
8+
9+
```ts
10+
const hasToken = request.headers.get('authorization')
11+
if (!hasToken) {
12+
return new Response('Unauthorized', {
13+
status: 401,
14+
headers: {
15+
'WWW-Authenticate': 'Bearer',
16+
},
17+
})
18+
}
19+
```
20+
21+
This check is the first and most basic requirement for a secure journal app. The `WWW-Authenticate` header in the response tells the client what kind of credentials are needed to try again.
22+
23+
<callout-info>
24+
If a request is missing the `Authorization` header, always include the
25+
`WWW-Authenticate` header in your 401 response. This helps clients know how to
26+
try again.
27+
</callout-info>
28+
29+
Without this check, nothing else about security matters. Make sure every request is challenged at the door.
30+
31+
πŸ“œ For more details, see the [MDN documentation on WWW-Authenticate](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate).
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
# Authenticate Header
2+
3+
πŸ‘¨β€πŸ’Ό Fantastic progress! We've just ensured that every request to our journal app is properly challenged for credentials. Now, if a client tries to access protected resources without an Authorization header, the server responds with a `401 Unauthorized` and a clear `WWW-Authenticate` header. This makes it obvious to clients what they need to do next, improving both security and user experience (while keeping us complient with the OAuth and MCP Authorization spec). Only authenticated users can move forward, keeping journal data safe and access predictable for everyone.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,18 @@
11
# Auth Params
2+
3+
πŸ‘¨β€πŸ’Ό In EpicMe, when a user tries to access a protected journal entry, it's not enough to simply block them. We need to let them know why. If a request is missing the right credentials, the server should respond with a `WWW-Authenticate` header that includes extra details, called auth params, so the client understands what went wrong and how to fix it.
4+
5+
For example, if a robot tries to fetch `/api/lemonade` without the right credentials, the response should include a realm and a resource_metadata parameter:
6+
7+
```
8+
WWW-Authenticate: Bearer realm="EpicMe", resource_metadata="https://lemonade-stand.com/.well-known/oauth-protected-resource/mcp"
9+
```
10+
11+
- **realm**: Identifies the protected area (like a journal or a lemonade stand) so clients know which resource needs credentials.
12+
- **resource_metadata**: A URL pointing to metadata about the protected resource, helping clients discover more about what they're trying to access.
13+
14+
This helps clients know not just that they're blocked, but also where to look for more information about the protected resource.
15+
16+
For more details, see the [MDN documentation on WWW-Authenticate](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate).
17+
18+
Let's make sure our API gives helpful feedback when things go wrong!

0 commit comments

Comments
Β (0)