Skip to content

Commit 7d7a4d1

Browse files
committed
solutions to 10c
1 parent 6613085 commit 7d7a4d1

File tree

10 files changed

+193
-643
lines changed

10 files changed

+193
-643
lines changed

asciidoc/courses/genai-mcp-build-custom-tools-python/modules/1-getting-started/lessons/1-mcp-python-sdk/lesson.adoc

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ The SDK includes two main approaches for building servers:
2929
* **FastMCP**: A high-level, decorator-based approach that makes it simple to create servers quickly
3030
* **Low-level server**: A more flexible approach that gives you full control over the MCP protocol
3131

32-
In this course, we will focus on the FastMCP approach.
32+
In this course, you will focus on the FastMCP approach.
3333

3434

3535
== Installing the MCP Python SDK
@@ -48,16 +48,16 @@ This package includes both the core MCP functionality and the FastMCP high-level
4848

4949
== Introducing FastMCP
5050

51-
FastMCP is the high-level interface in the MCP Python SDK designed to make server development as simple as possible. FastMCP is built on top of the FastAPI framework, and uses a decorator approach to defining MCP features.
51+
link:https://gofastmcp.com/[FastMCP^] is the high-level interface in the MCP Python SDK designed to make server development as simple as possible. It is built on top of the FastAPI framework, and uses a decorator approach to defining MCP features.
5252

5353

5454
== Core MCP Features
5555

56-
MCP servers expose three types of features to clients. Let's take a look at each one and when to use them.
56+
MCP servers expose three types of features to clients.
5757

5858
=== 1. Tools
5959

60-
**Tools** are functions that LLMs can call to perform actions or retrieve data. They are perfect for tasks that LLMs struggle with, like counting or complex calculations.
60+
**Tools** are functions that LLMs can call to perform actions or retrieve data. Tools are perfect for tasks that LLMs struggle with, like counting or complex calculations.
6161

6262
**Characteristics:**
6363

@@ -204,7 +204,7 @@ Reflection is then used to infer metadata about the tool.
204204
2. The output of the tool is an `int`
205205
3. The string in the opening line is used to describe what the tool does and and when it should be used.
206206

207-
The `@mcp.tool()` decorator accepts a number of optional arguments, which we will cover later in the course.
207+
The `@mcp.tool()` decorator accepts a number of optional arguments, which you will learn about later in the course.
208208

209209

210210
== Running the server
@@ -240,13 +240,16 @@ link:https://github.com/jlowin/fastmcp[Learn more about `fastmcp`].
240240

241241
=== Transport methods
242242

243-
In the previous course, we also covered the different transport methods that can be used to connect to an MCP server; Standard Input/Output (`stdio`), and Streamable HTTP (`http`).
244-
As we will develop a local MCP server in this course, we will focus on the `stdio` transport method. You can change the transport method by passing the `transport` parameter to the `run` method.
243+
In the link:https://graphacademy.neo4j.com/courses/genai-mcp-neo4j-tools/[Developing with Neo4j MCP Tools course^], we also covered the different transport methods that can be used to connect to an MCP server; Standard Input/Output (`stdio`), and Streamable HTTP (`http`).
244+
245+
This course will focus on the **Streamable HTTP** transport method.
246+
The Streamable HTTP transport method exposes the tools through a HTTP server, with results streamed back to the client using link:https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events[Server-Sent Events (SSE)^].
247+
245248

246249
[source,python]
247250
----
248251
mcp.run(
249-
transport="http",
252+
transport="streamable-http",
250253
host="127.0.0.1",
251254
port=8000,
252255
path="/mcp"
@@ -258,13 +261,20 @@ Streaming HTTP is recommended for web deployments.
258261
[TIP]
259262
.The `fastmcp` command line tool
260263
====
261-
You can also provide the `--transport`, `--host`, `--port`, and `--path` flags to the `fastmcp` command.
264+
You can change the transport method by providing the `--transport`, `--host`, `--port`, and `--path` flags to the `fastmcp` command.
265+
The command doesn't execute the `__main__` block of the server, instead preferring the parameters passed to the command.
266+
267+
[source,bash]
268+
----
269+
fastmcp run server.py --transport http --host 127.0.0.1 --port 8000
270+
----
271+
272+
link:https://gofastmcp.com/patterns/cli/[Learn more about the `fastmcp` command line tool^].
262273
====
263274

264275
read::Mark as Completed[]
265276

266277

267-
268278
[.summary]
269279
== Summary
270280

asciidoc/courses/genai-mcp-build-custom-tools-python/modules/1-getting-started/lessons/3c-create-first-server/code/solution.py

Lines changed: 0 additions & 32 deletions
This file was deleted.

asciidoc/courses/genai-mcp-build-custom-tools-python/modules/2-database-features/lessons/10c-paginated-tool/lesson.adoc

Lines changed: 77 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
:type: challenge
33
:order: 10
44

5-
65
In the previous lesson, you learned about pagination and how to use Neo4j's `SKIP` and `LIMIT` clauses to fetch data in manageable chunks.
76

87
In this challenge, you will implement a paginated tool that allows users to browse through movies in a specific genre, page by page.
@@ -41,111 +40,42 @@ First, define the tool function with its parameters:
4140
[source,python]
4241
.server/main.py
4342
----
44-
@mcp.tool()
45-
async def browse_movies_by_genre(
46-
genre: str,
47-
cursor: str = "0",
48-
page_size: int = 10,
49-
ctx: Context = None
50-
) -> dict:
51-
"""
52-
Browse movies in a genre with pagination support.
53-
54-
Args:
55-
genre: Genre name (e.g., "Action", "Comedy", "Drama")
56-
cursor: Pagination cursor - position in the result set (default "0")
57-
page_size: Number of movies to return per page (default 10)
58-
59-
Returns:
60-
Dictionary containing:
61-
- movies: List of movie objects with title, released, and rating
62-
- next_cursor: Cursor for the next page (null if no more pages)
63-
- page: Current page number (1-indexed)
64-
- has_more: Boolean indicating if more pages are available
65-
"""
43+
include::{repository-raw}/main/solutions/10c-paginated-tool/main.py[tag=list_movies_by_genre_def]
6644
----
6745

68-
The function takes a required `genre` parameter and optional `cursor` and `page_size` parameters. The cursor defaults to "0" (start of the list), and page_size defaults to 10 items per page.
46+
The function takes a required `genre` parameter and optional `cursor` and `page_size` parameters. The cursor defaults to `0` (start of the list), and page_size defaults to 10 items per page.
6947

7048
Next, handle cursor validation and setup:
7149

7250
[source,python]
7351
----
74-
# Parse cursor to get skip value
75-
try:
76-
skip = int(cursor)
77-
except ValueError:
78-
await ctx.error(f"Invalid cursor: {cursor}")
79-
skip = 0
80-
81-
# Access driver from lifespan context
82-
driver = ctx.request_context.lifespan_context.driver
83-
84-
# Log the request
85-
page_num = (skip // page_size) + 1
86-
await ctx.info(f"Fetching {genre} movies, page {page_num} (showing {page_size} per page)...")
52+
include::{repository-raw}/main/solutions/10c-paginated-tool/main.py[tag=list_movies_by_genre_cursor]
8753
----
8854

89-
This section converts the cursor string to a number and calculates the current page number. If the cursor is invalid, it defaults to the start (skip = 0).
55+
This section uses the cursor number to calculate the skip value.
9056

91-
The query execution uses Neo4j's SKIP and LIMIT for pagination:
57+
Next, using the driver instance from the lifespan context, execute the query to fetch the movies using the SKIP and LIMIT clauses, and coerce the results into a list of dictionaries.
9258

9359
[source,python]
9460
----
95-
try:
96-
# Execute paginated query
97-
records, summary, keys = await driver.execute_query(
98-
"""
99-
MATCH (m:Movie)-[:IN_GENRE]->(g:Genre {name: $genre})
100-
RETURN m.title AS title,
101-
m.released AS released,
102-
m.imdbRating AS rating
103-
ORDER BY m.imdbRating DESC, m.title ASC
104-
SKIP $skip
105-
LIMIT $limit
106-
""",
107-
genre=genre,
108-
skip=skip,
109-
limit=page_size
110-
)
61+
include::{repository-raw}/main/solutions/10c-paginated-tool/main.py[tag=list_movies_by_genre_execute]
11162
----
11263

113-
The Cypher query finds movies in the specified genre, ordered by rating (highest first) and title. SKIP and LIMIT handle the pagination.
114-
115-
Finally, process the results and return the paginated response:
64+
Finally, calculate the next cursor and return the structured output.
11665

11766
[source,python]
11867
----
119-
# Convert to list of dictionaries
120-
movies = [record.data() for record in records]
121-
122-
# Calculate next cursor
123-
next_cursor = None
124-
if len(movies) == page_size:
125-
next_cursor = str(skip + page_size)
126-
127-
# Log results
128-
await ctx.info(f"Returned {len(movies)} movies from page {page_num}")
129-
if next_cursor is None:
130-
await ctx.info("This is the last page")
131-
132-
# Return structured response
133-
return {
134-
"genre": genre,
135-
"movies": movies,
136-
"next_cursor": next_cursor,
137-
"page": page_num,
138-
"page_size": page_size,
139-
"has_more": next_cursor is not None,
140-
"count": len(movies)
141-
}
142-
143-
except Exception as e:
144-
await ctx.error(f"Query failed: {str(e)}")
145-
raise
68+
include::{repository-raw}/main/solutions/10c-paginated-tool/main.py[tag=list_movies_by_genre_return]
14669
----
14770

148-
The response includes the movies list and pagination metadata. The `next_cursor` is only set if a full page was returned, indicating more results are available.
71+
The structured output consists of a dictionary with the following keys:
72+
73+
* `genre` - The genre passed to the tool by the client
74+
* `movies` - A list of movies returned from the query
75+
* `next_cursor` - The cursor for the next page
76+
* `page` - The current page number
77+
* `page_size` - The number of movies per page
78+
* `has_more` - A boolean indicating if more pages are available
14979

15080

15181
== Step 2: Test with the Interactive Client
@@ -177,21 +107,41 @@ genre (required)
177107
Type: string
178108
Enter value: Action
179109
180-
cursor (optional, default: 0)
110+
page_size (optional)
111+
Type: integer
112+
Enter value: 2
113+
114+
cursor (optional)
181115
Type: string
182-
Enter value: 0
116+
Enter value: 1=9
183117
184-
page_size (optional, default: 10)
185-
Type: integer
186-
Enter value: 10
187118
----
188119

189-
The response should contain:
120+
You should receive a structured response similar to the following, with information about the current page and the next cursor.
190121

191-
* 10 Action movies
192-
* A `next_cursor` value (e.g., `"10"`)
193-
* `page: 1`
194-
* `has_more: true`
122+
[source,json]
123+
.Structured Response
124+
----
125+
{
126+
"genre": "Action",
127+
"movies": [
128+
{
129+
"title": "'Hellboy': The Seeds of Creation",
130+
"released": "2004-07-27",
131+
"rating": 6.9
132+
},
133+
{
134+
"title": "13 Assassins (Jûsan-nin no shikaku)",
135+
"released": "2010-09-25",
136+
"rating": 7.6
137+
}
138+
],
139+
"next_cursor": 2,
140+
"page": 1,
141+
"page_size": 2,
142+
"has_more": true
143+
}
144+
----
195145

196146

197147
=== Fetch the Second Page
@@ -204,45 +154,49 @@ genre (required)
204154
Type: string
205155
Enter value: Action
206156
207-
cursor (optional, default: 0)
208-
Type: string
209-
Enter value: 10
210-
211157
page_size (optional, default: 10)
212158
Type: integer
213-
Enter value: 10
214-
----
215-
216-
The response should contain:
217-
218-
* The next 10 Action movies
219-
* A new `next_cursor` value (e.g., `"20"`)
220-
* `page: 2`
221-
* `has_more: true` (if more pages exist)
222-
223-
224-
=== Continue to the Last Page
159+
Enter value: 2
225160
226-
Keep using the `next_cursor` until you reach a response where:
227-
228-
* `next_cursor` is `null` or not present
229-
* `has_more` is `false`
230-
* Fewer than `page_size` movies are returned
161+
cursor (optional, default: 0)
162+
Type: string
163+
Enter value: 2
231164
165+
----
232166

233-
== Step 3: Test with Different Genres
167+
The response should contain a different page number, next cursor and list of movies.
234168

235-
Try different genres to see how pagination behaves:
169+
[source,json]
170+
.Paginated Response
171+
----
172+
{
173+
"genre": "Action",
174+
"movies": [
175+
{
176+
"title": "2 Fast 2 Furious (Fast and the Furious 2, The)",
177+
"released": "2003-06-06",
178+
"rating": 5.8
179+
},
180+
{
181+
"title": "2 Guns",
182+
"released": "2013-08-02",
183+
"rating": 6.7
184+
}
185+
],
186+
"next_cursor": 6,
187+
"page": 3,
188+
"page_size": 2,
189+
"has_more": true
190+
}
191+
----
236192

237-
* `"Comedy"` - Might have many pages
238-
* `"Sci-Fi"` - Moderate number of pages
239-
* `"Documentary"` - Might fit in a single page
240193

241-
Notice how some genres have more movies than others!
194+
Experiment with different genres, for example [copy]#Comedy#, [copy]#Sci-Fi#, or [copy]#Documentary#, and change the [copy]#page_size# to see how it affects the results.
242195

243196

244197
read::I have pagination![]
245198

199+
// TODO: Update this to add a COUNT {} subquery for the total? Nice use of degree counts...
246200

247201
[.summary]
248202
== Summary

asciidoc/courses/genai-mcp-build-custom-tools-python/modules/2-database-features/lessons/2c-add-neo4j-connection/lesson.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ Create a `graph_statistics` tool function that uses `ctx.request_context.lifespa
120120
[source,python]
121121
.server/main.py
122122
----
123-
include::{repository-raw}/main/solutions/2c-add-neo4j-connection/main.py[tag=tool]
123+
include::{repository-raw}/main/solutions/2c-add-neo4j-connection/main.py[tag=graph_statistics]
124124
----
125125

126126
[TIP]
@@ -129,7 +129,7 @@ include::{repository-raw}/main/solutions/2c-add-neo4j-connection/main.py[tag=too
129129
The `database_` parameter is used to specify the database to execute the query on.
130130
Any named arguments that do not end with an underscore will be passed as parameters to the Cypher query.
131131
132-
You can link:/courses/drivers-python/[learn more about the Driverin the Using Neo4j with Python course^].
132+
You can link:/courses/drivers-python/[learn more about the Driver in the Using Neo4j with Python course^].
133133
====
134134

135135

0 commit comments

Comments
 (0)