Skip to content

Commit 4960b4b

Browse files
committed
finish exercise 4 instructions
1 parent 9e3894e commit 4960b4b

File tree

7 files changed

+88
-2
lines changed

7 files changed

+88
-2
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,57 @@
11
# Client Token
2+
3+
👨‍💼 When we make requests using `this.db` (our db client), we need to include the user's authentication token so that the server knows who the user is and what they're allowed to access.
4+
5+
Right now the db client doesn't accept a token, so you need to update it to do so.
6+
7+
```ts
8+
// Create a client that knows who the user is
9+
const db = getClient(userOAuthToken)
10+
const entries = await db.getEntries() // Only gets THIS user's entries
11+
```
12+
13+
<callout-warning class="important">
14+
If there's no authentication token, we shouldn't even try to create a database
15+
connection. The user needs to be properly authenticated first!
16+
</callout-warning>
17+
18+
```mermaid
19+
sequenceDiagram
20+
MCP Client->>MCP Server: Requests journal entries
21+
MCP Server->>Auth: Validates OAuth token
22+
Auth-->>MCP Server: Returns user identity
23+
MCP Server->>MCP Server: Create client with token
24+
MCP Server->>DB: get entries (with token)
25+
DB-->>MCP Server: Returns user's entries only
26+
MCP Server-->>MCP Client: Shows personal journal entries
27+
```
28+
29+
<callout-muted>
30+
📜 For more details on OAuth tokens and authentication flows, see the [OAuth
31+
2.0 specification](https://tools.ietf.org/html/rfc6749).
32+
</callout-muted>
33+
34+
The tricky part of this is how to get the auth info we get from the request into `EpicMeMCP` (our instance of the `McpAgent`). To do this with Cloudflare, we set the `ctx.props`:
35+
36+
```ts lines=1-3,5,15
37+
type State = {}
38+
type Props = { greeting: string }
39+
export class MyMCP extends McpAgent<Env, State, Props> {
40+
async init() {
41+
console.log(this.props?.greeting)
42+
}
43+
}
44+
45+
export default {
46+
fetch: async (request, env, ctx) => {
47+
// ...
48+
const url = new URL(request.url)
49+
const mcp = MyMCP.serve('/mcp')
50+
51+
ctx.props.greeting = 'Hello, world!'
52+
return mcp.fetch(request, env, ctx)
53+
},
54+
} satisfies ExportedHandler<Env>
55+
```
56+
57+
Note, out of an abundance of caution, the types for `this.props` is `Props | undefined`. This means you need to check if it's undefined before using it. I recommend using the `invariant` function to throw an error if it's `undefined` because that should really never happen.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
# Client Token
2+
3+
👨‍💼 Excellent work! We've successfully implemented user-specific database access by passing authentication tokens to our database client. This is a crucial step in securing our MCP server. It ensures each user only sees their own data.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
11
# Require User
2+
3+
👨‍💼 Now that we have the client authenticated with the token, let's make a utility to get the user and also provide tools and resources to retrieve the user data.
4+
5+
🧝‍♀️ I created a resource and a tool for you, all you need to do is implement the `requireUser` utility then use that in the tool and resource. Feel free to <PrevDiffLink>check out my changes</PrevDiffLink> if you want.
6+
7+
👨‍💼 Great! Ok, so the goal is to make user data accessible throughout our MCP server while maintaining security and providing a seamless, personalized experience for every journaling session.
8+
9+
Now, implement the `requireUser` utility so our tools and resources can provide users with their personalized journaling experience.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
# Require User
2+
3+
👨‍💼 Excellent work! Now clients can retrieve information about the currently logged in user.

exercises/04.user/FINISHED.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
# Using the User
2+
3+
Congratulations! You've successfully implemented user-specific functionality in your MCP server. You've moved from a generic MCP server into a personalized, secure system that knows who each user is and ensures they only access their own data.
4+
5+
You also now understand how to pass authentication information through Cloudflare's context system using `ctx.props`, and then using that information to create user-specific database clients and utility functions to access it in the tools, resources, and prompts.
6+
7+
Let's keep moving to continue building on this foundation with scope-based permissions.

exercises/05.scopes/03.solution.scope-hints/src/resources.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ export async function initializeResources(agent: EpicMeMCP) {
1414
contents: [
1515
{
1616
mimeType: 'application/json',
17-
text: JSON.stringify(user),
17+
text: JSON.stringify({
18+
user,
19+
scopes: agent.requireAuthInfo().scopes,
20+
}),
1821
uri: uri.toString(),
1922
},
2023
],

exercises/05.scopes/03.solution.scope-hints/src/tools.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,16 @@ export async function initializeTools(agent: EpicMeMCP) {
4040
}
4141
return {
4242
structuredContent,
43-
content: [createText(structuredContent)],
43+
content: [
44+
{
45+
type: 'resource_link',
46+
uri: 'epicme://users/current',
47+
name: 'Current User',
48+
description: 'Info on the currently logged in user',
49+
mimeType: 'application/json',
50+
},
51+
createText(structuredContent),
52+
],
4453
}
4554
},
4655
)

0 commit comments

Comments
 (0)