From 4a55bdab3b8a220841a0a10598c424188fc5b091 Mon Sep 17 00:00:00 2001
From: Stephen Walsh <868017+stphnwlsh@users.noreply.github.com>
Date: Fri, 6 Nov 2026 23:00:59 +0000
Subject: [PATCH 1/3] Bring the docs up to date with review text, review search
and the new MCP features
The sample movies have recognisable titles now, so every example shows the
new ids and numbers. Reviews have text and can be searched by it and by a
minimum star rating. MCP has two more prompts, a top rated resource, id
completions, resource links, icons, null fields and an optional chat demo.
dotnet run listens on port 8080 now, not 5032.
---
src/content/docs/graphql/getting-started.mdx | 18 +++----
.../docs/graphql/guides/add-a-field.mdx | 8 +--
.../docs/graphql/guides/run-locally.mdx | 12 ++---
.../docs/graphql/guides/secure-the-api.mdx | 2 +-
.../docs/graphql/reference/configuration.mdx | 2 +-
src/content/docs/graphql/reference/schema.mdx | 23 +++++----
src/content/docs/lambda/getting-started.mdx | 11 ++--
.../docs/lambda/guides/add-a-function.mdx | 2 +-
.../docs/lambda/guides/run-locally.mdx | 2 +-
.../docs/lambda/reference/functions.mdx | 44 +++++++++-------
.../docs/mcp/explanation/decisions.mdx | 2 +-
src/content/docs/mcp/getting-started.mdx | 15 +++---
src/content/docs/mcp/guides/add-a-tool.mdx | 12 ++---
.../docs/mcp/guides/connect-a-client.mdx | 16 +++---
src/content/docs/mcp/guides/run-locally.mdx | 33 ++++++++----
.../docs/mcp/guides/secure-the-server.mdx | 2 +-
src/content/docs/mcp/index.mdx | 2 +-
.../docs/mcp/reference/configuration.mdx | 2 +-
.../mcp/reference/resources-and-prompts.mdx | 25 ++++++---
src/content/docs/mcp/reference/tools.mdx | 27 ++++++----
.../docs/minimalapi/getting-started.mdx | 12 ++---
.../minimalapi/guides/add-an-endpoint.mdx | 10 ++--
.../docs/minimalapi/guides/run-locally.mdx | 12 ++---
.../docs/minimalapi/guides/secure-the-api.mdx | 2 +-
.../minimalapi/reference/configuration.mdx | 2 +-
.../docs/minimalapi/reference/endpoints.mdx | 51 +++++++++++++------
src/content/docs/webapi/getting-started.mdx | 10 ++--
.../docs/webapi/guides/add-an-endpoint.mdx | 10 ++--
.../docs/webapi/guides/run-locally.mdx | 6 +--
.../docs/webapi/reference/endpoints.mdx | 49 +++++++++++++-----
30 files changed, 254 insertions(+), 170 deletions(-)
diff --git a/src/content/docs/graphql/getting-started.mdx b/src/content/docs/graphql/getting-started.mdx
index 86e3d30..993ed04 100644
--- a/src/content/docs/graphql/getting-started.mdx
+++ b/src/content/docs/graphql/getting-started.mdx
@@ -55,11 +55,11 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
-5. Open Nitro, the GraphQL IDE, at [http://localhost:5032/graphql](http://localhost:5032/graphql). The schema is on the left, with every type and field described, and the editor autocompletes as you type. Paste this in and press play:
+5. Open Nitro, the GraphQL IDE, at [http://localhost:8080/graphql](http://localhost:8080/graphql). The schema is on the left, with every type and field described, and the editor autocompletes as you type. Paste this in and press play:
```graphql
{
- movies(query: "global", first: 2) {
+ movies(query: "space", first: 2) {
totalCount
pageInfo { hasNextPage endCursor }
nodes { id title reviewCount averageStars }
@@ -70,8 +70,8 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
6. Or ask from the terminal.
```bash
- curl -X POST "http://localhost:5032/graphql" -H "Content-Type: application/json" \
- -d '{"query":"{ movies(query: \"global\", first: 2) { totalCount nodes { id title reviewCount averageStars } } }"}'
+ curl -X POST "http://localhost:8080/graphql" -H "Content-Type: application/json" \
+ -d '{"query":"{ movies(query: \"space\", first: 2) { totalCount nodes { id title reviewCount averageStars } } }"}'
```
Either way you get a page of movies back:
@@ -80,18 +80,18 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
{
"data": {
"movies": {
- "totalCount": 2,
- "pageInfo": { "hasNextPage": false, "endCursor": "b2Zmc2V0OjI=" },
+ "totalCount": 4,
+ "pageInfo": { "hasNextPage": true, "endCursor": "b2Zmc2V0OjI=" },
"nodes": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
]
}
}
}
```
- Yes, the movies are named by a corporate buzzword generator. *Assimilated global infrastructure* only got one star. Critics can be harsh.
+ The sample movies are all made up. *Deep Space Gardeners* averages 3.4 stars from five reviews, which seems fair for a film about weeding in zero gravity.
7. Run the tests while you're here.
diff --git a/src/content/docs/graphql/guides/add-a-field.mdx b/src/content/docs/graphql/guides/add-a-field.mdx
index 9e5ea2a..d93bb46 100644
--- a/src/content/docs/graphql/guides/add-a-field.mdx
+++ b/src/content/docs/graphql/guides/add-a-field.mdx
@@ -60,7 +60,7 @@ This guide adds `bestReview` to the `Movie` type: the highest rated review of th
```bash
dotnet run --project ./src/Presentation
- curl "http://localhost:5032/graphql?sdl" > tests/Presentation.Tests.Integration/GraphQL/Schema/schema.graphql
+ curl "http://localhost:8080/graphql?sdl" > tests/Presentation.Tests.Integration/GraphQL/Schema/schema.graphql
```
The diff is the one field, described:
@@ -117,7 +117,7 @@ This guide adds `bestReview` to the `Movie` type: the highest rated review of th
```graphql
{
- movie(id: "e159975e-4e6a-1ffd-713e-1eb9d8d4a593") {
+ movie(id: "cb188ef6-14e7-f85f-2f78-924c2ab26b28") {
title
bestReview { stars author { firstName lastName } }
}
@@ -125,10 +125,10 @@ This guide adds `bestReview` to the `Movie` type: the highest rated review of th
```
```json
- { "data": { "movie": { "title": "Assimilated disintermediate adapter", "bestReview": { "stars": 5, "author": { "firstName": "Jana", "lastName": "Auer" } } } } }
+ { "data": { "movie": { "title": "Deep Space Gardeners", "bestReview": { "stars": 5, "author": { "firstName": "Caroline", "lastName": "Bins" } } } } }
```
- Jana gave it five stars. Jana's average is 2.6, so this is high praise.
+ Caroline gave it five stars, and three in a second review. Caroline contains multitudes.
diff --git a/src/content/docs/graphql/guides/run-locally.mdx b/src/content/docs/graphql/guides/run-locally.mdx
index 2044e00..b0f362b 100644
--- a/src/content/docs/graphql/guides/run-locally.mdx
+++ b/src/content/docs/graphql/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:5032](http://localhost:5032) and `https://localhost:7032`.
+ `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`, the same port as Docker Compose.
```bash
@@ -66,9 +66,9 @@ Nitro is the easy way: it knows the schema, autocompletes, and keeps your query
```graphql
# One movie, its reviews and who wrote them
{
- movie(id: "e159975e-4e6a-1ffd-713e-1eb9d8d4a593") {
+ movie(id: "cb188ef6-14e7-f85f-2f78-924c2ab26b28") {
title
- reviews { stars author { firstName lastName } }
+ reviews { stars text author { firstName lastName } }
}
}
```
@@ -76,8 +76,8 @@ Nitro is the easy way: it knows the schema, autocompletes, and keeps your query
```graphql
# Write a review
mutation {
- createReview(input: { authorId: "01fa8b58-05d2-abd4-e55c-2633222dd0a5", movieId: "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", stars: 5 }) {
- review { id stars }
+ createReview(input: { authorId: "01fa8b58-05d2-abd4-e55c-2633222dd0a5", movieId: "cb188ef6-14e7-f85f-2f78-924c2ab26b28", stars: 5, text: "Great film. I cried at the ending." }) {
+ review { id stars text }
errors { __typename ... on Error { message } }
}
}
@@ -117,5 +117,5 @@ docker build . --target run --tag acme-movies:latest \
The `version` query then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/graphql/guides/secure-the-api.mdx b/src/content/docs/graphql/guides/secure-the-api.mdx
index 2593ed7..feadb9d 100644
--- a/src/content/docs/graphql/guides/secure-the-api.mdx
+++ b/src/content/docs/graphql/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl -X POST "http://localhost:5032/graphql" -H "Authorization: Bearer eyJhbGciOi..." \
+ curl -X POST "http://localhost:8080/graphql" -H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" -d '{"query":"{ movies { totalCount } }"}'
```
diff --git a/src/content/docs/graphql/reference/configuration.mdx b/src/content/docs/graphql/reference/configuration.mdx
index 2d06148..6c44903 100644
--- a/src/content/docs/graphql/reference/configuration.mdx
+++ b/src/content/docs/graphql/reference/configuration.mdx
@@ -55,4 +55,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:5032` and `https://localhost:7032`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8080` and `https://localhost:8081`.
diff --git a/src/content/docs/graphql/reference/schema.mdx b/src/content/docs/graphql/reference/schema.mdx
index ff86b77..a98c98f 100644
--- a/src/content/docs/graphql/reference/schema.mdx
+++ b/src/content/docs/graphql/reference/schema.mdx
@@ -5,7 +5,7 @@ sidebar:
order: 1
---
-The schema lives in `src/Presentation/GraphQL`: queries in `Queries`, mutations in `Mutations`, object types in `Types` and the errors mutations return in `Errors`. The endpoint is `/graphql`, which also serves Nitro to a browser, and `/graphql?sdl` returns the schema as SDL. Every query and mutation, `version` included, requires authorization, which only has an effect when [authentication is on](../../guides/secure-the-api/).
+The schema lives in `src/Presentation/GraphQL`: queries in `Queries`, mutations in `Mutations`, object types in `Types` and the errors mutations return in `Errors`. The endpoint is `/graphql`, which also serves Nitro to a browser, and `/graphql?sdl` returns the schema as SDL. In a solution created with `--include-auth`, every query and mutation, `version` included, needs a token (see [Secure the API](../../guides/secure-the-api/)).
Ids are the `UUID` scalar. Field and type descriptions come from the XML comments on the code, so Nitro shows the same text as this page.
@@ -18,14 +18,14 @@ Ids are the `UUID` scalar. Field and type descriptions come from the XML comment
| `author` | `id: UUID!` | [`Author`](#author), or null when there's no author with that id |
| `authors` | `query: String`, `first: Int`, `after: String` | A [connection](#connections-and-paging) of [`AuthorSummary`](#authorsummary), ordered by last name then first name |
| `review` | `id: UUID!` | [`Review`](#review), or null when there's no review with that id |
-| `reviews` | `movieId: UUID`, `authorId: UUID`, `first: Int`, `after: String` | A [connection](#connections-and-paging) of [`Review`](#review), ordered by movie then author |
+| `reviews` | `movieId: UUID`, `authorId: UUID`, `query: String`, `minStars: Int`, `first: Int`, `after: String` | A [connection](#connections-and-paging) of [`Review`](#review), ordered by movie then author |
| `version` | none | [`VersionInfo!`](#versioninfo) |
-`query` matches anywhere in the title or the full name, ignoring case, and can be up to 100 characters. `movieId` and `authorId` filter the reviews to one movie, one author, or both.
+`query` matches anywhere in the title, the full name or, on `reviews`, the review text, ignoring case, and can be up to 100 characters. `movieId` and `authorId` filter the reviews to one movie, one author, or both, and `minStars`, from 1 to 5, keeps only reviews with at least that many stars.
```graphql
{
- movies(query: "global", first: 2) {
+ movies(query: "space", first: 2) {
totalCount
pageInfo { hasNextPage endCursor }
nodes { id title reviewCount averageStars }
@@ -37,11 +37,11 @@ Ids are the `UUID` scalar. Field and type descriptions come from the XML comment
{
"data": {
"movies": {
- "totalCount": 2,
- "pageInfo": { "hasNextPage": false, "endCursor": "b2Zmc2V0OjI=" },
+ "totalCount": 4,
+ "pageInfo": { "hasNextPage": true, "endCursor": "b2Zmc2V0OjI=" },
"nodes": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
]
}
}
@@ -60,10 +60,11 @@ The mutations follow Hot Chocolate's [mutation conventions](https://chillicream.
```graphql
mutation {
- createReview(input: { authorId: "01fa8b58-05d2-abd4-e55c-2633222dd0a5", movieId: "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", stars: 5 }) {
+ createReview(input: { authorId: "01fa8b58-05d2-abd4-e55c-2633222dd0a5", movieId: "cb188ef6-14e7-f85f-2f78-924c2ab26b28", stars: 5, text: "Great film. I cried at the ending." }) {
review {
id
stars
+ text
movie { title }
author { firstName lastName }
}
@@ -150,6 +151,7 @@ Paging only goes forwards. There's no `last` or `before`, and `hasPreviousPage`
| --- | --- | --- |
| `id` | `UUID!` | The review's id |
| `stars` | `Int!` | The rating, from 1 to 5 |
+| `text` | `String` | What the author said about the movie, or null when there's no text |
| `movie` | `ReviewedMovie` | The movie's `id` and `title` |
| `author` | `ReviewAuthor` | The author's `id`, `firstName` and `lastName` |
@@ -167,6 +169,7 @@ Paging only goes forwards. There's no `last` or `before`, and `hasPreviousPage`
| `authorId` | `UUID!` | An existing author |
| `movieId` | `UUID!` | An existing movie |
| `stars` | `Int!` | 1 to 5 |
+| `text` | `String` | Optional. Up to 1000 characters |
### UpdateReviewInput
@@ -174,6 +177,7 @@ Paging only goes forwards. There's no `last` or `before`, and `hasPreviousPage`
| --- | --- | --- |
| `id` | `UUID!` | An existing review |
| `stars` | `Int!` | 1 to 5 |
+| `text` | `String` | Optional. Up to 1000 characters. Leave it out to keep the text the review has |
### DeleteReviewInput
@@ -256,3 +260,4 @@ A query with no match doesn't error. `movie` and `author` return null, and the l
| Page size | 10 by default, 50 at most |
| Query depth | 15 |
| Search term | 100 characters |
+| Review text | 1000 characters |
diff --git a/src/content/docs/lambda/getting-started.mdx b/src/content/docs/lambda/getting-started.mdx
index f1b5ba2..8714c46 100644
--- a/src/content/docs/lambda/getting-started.mdx
+++ b/src/content/docs/lambda/getting-started.mdx
@@ -59,7 +59,7 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) an
```bash
curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" \
- -d '{"query":"global","limit":2}'
+ -d '{"query":"space","limit":2}'
```
You get a page of movies back:
@@ -67,14 +67,15 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) an
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
],
- "totalCount": 2
+ "totalCount": 4,
+ "nextOffset": 2
}
```
- Yes, the movies are named by a corporate buzzword generator. *Assimilated global infrastructure* only got one star. Critics can be harsh.
+ The sample movies are all made up. *Deep Space Gardeners* averages 3.4 stars from five reviews, which seems fair for a film about weeding in zero gravity.
6. Run the tests while you're here.
diff --git a/src/content/docs/lambda/guides/add-a-function.mdx b/src/content/docs/lambda/guides/add-a-function.mdx
index 360b530..aa5c5b2 100644
--- a/src/content/docs/lambda/guides/add-a-function.mdx
+++ b/src/content/docs/lambda/guides/add-a-function.mdx
@@ -107,7 +107,7 @@ This guide adds a `GetMovie` function that returns one movie with all of its rev
"Presentation::WorksOnMyMachine.Reviews.Presentation.LambdaFunction.Handler_GetMovie_Generated::GetMovie"
curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" \
- -d '{"id":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593"}'
+ -d '{"id":"cb188ef6-14e7-f85f-2f78-924c2ab26b28"}'
```
diff --git a/src/content/docs/lambda/guides/run-locally.mdx b/src/content/docs/lambda/guides/run-locally.mdx
index 03f4f30..5eda99b 100644
--- a/src/content/docs/lambda/guides/run-locally.mdx
+++ b/src/content/docs/lambda/guides/run-locally.mdx
@@ -48,7 +48,7 @@ docker run --rm -p 9000:8080 -e Database__SeedSampleData=true acme-reviews:local
"Presentation::WorksOnMyMachine.Reviews.Presentation.LambdaFunction.Handler_GetReviews_Generated::GetReviews"
curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" \
- -d '{"movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","limit":2}'
+ -d '{"movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","limit":2}'
```
The sample data uses a fixed seed, so that movie id is the same on every machine. See the [function reference](../../reference/functions/) for every request and response.
diff --git a/src/content/docs/lambda/reference/functions.mdx b/src/content/docs/lambda/reference/functions.mdx
index 85c9c3a..92e9094 100644
--- a/src/content/docs/lambda/reference/functions.mdx
+++ b/src/content/docs/lambda/reference/functions.mdx
@@ -33,7 +33,7 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```json
-{ "query": "global", "offset": 0, "limit": 2 }
+{ "query": "space", "offset": 0, "limit": 2 }
```
### Response
@@ -50,10 +50,11 @@ A [page](#pages) of movies.
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
],
- "totalCount": 2
+ "totalCount": 4,
+ "nextOffset": 2
}
```
@@ -80,8 +81,8 @@ A [page](#pages) of authors.
```json
{
"items": [
- { "id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "firstName": "Jana", "lastName": "Auer", "reviewCount": 10, "averageStars": 2.6 },
- { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 12, "averageStars": 2.7 }
+ { "id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "firstName": "Jana", "lastName": "Auer", "reviewCount": 9, "averageStars": 3.3 },
+ { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 10, "averageStars": 3.2 }
],
"totalCount": 15,
"nextOffset": 2
@@ -90,7 +91,7 @@ A [page](#pages) of authors.
## GetReviews
-Lists reviews ordered by movie then author, one page at a time, optionally only those of one movie, by one author, or both.
+Lists reviews ordered by movie then author, one page at a time. Filter by movie, by author, by words in the review text and by a minimum star rating, in any combination.
### Request
@@ -98,11 +99,13 @@ Lists reviews ordered by movie then author, one page at a time, optionally only
| --- | --- | --- | --- |
| `movieId` | guid | none | Only reviews of this movie |
| `authorId` | guid | none | Only reviews by this author |
+| `query` | string | none | Up to 100 characters. Matches the review text, ignoring case |
+| `minStars` | integer | none | 1 to 5. Only reviews with at least this many stars |
| `offset` | integer | `0` | 0 or more |
| `limit` | integer | `10` | 1 to 50 |
```json
-{ "movieId": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "limit": 2 }
+{ "movieId": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "limit": 2 }
```
### Response
@@ -115,24 +118,27 @@ A [page](#pages) of reviews.
| `stars` | integer | The rating, from 1 to 5 |
| `reviewedMovie` | object | The reviewed movie's `id` and `title`. Its `reviewCount` is always 0 here, so use GetMovies for the real numbers |
| `reviewAuthor` | object | The author's `id`, `firstName` and `lastName`. Its `reviewCount` is always 0 here, so use GetAuthors for the real numbers |
+| `text` | string | What the author said about the movie. Left out when there's no text |
```json
{
"items": [
{
- "id": "969cb90c-95ae-9189-1e52-0b38d34a911b",
- "stars": 4,
- "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter", "reviewCount": 0 },
- "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 0 }
+ "id": "45911e6f-a366-417b-1453-f34097338ee2",
+ "stars": 5,
+ "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 0 },
+ "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 0 },
+ "text": "Everyone involved should be very proud."
},
{
- "id": "a5ebd747-0408-0a96-1af1-eb5cb93bc814",
- "stars": 1,
- "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter", "reviewCount": 0 },
- "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 0 }
+ "id": "8832846c-4100-e92a-0b66-e59dcfde2acc",
+ "stars": 3,
+ "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 0 },
+ "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 0 },
+ "text": "I laughed twice and looked at my phone once."
}
],
- "totalCount": 6,
+ "totalCount": 5,
"nextOffset": 2
}
```
@@ -172,10 +178,10 @@ The three lists are also on an [API Gateway HTTP API](https://docs.aws.amazon.co
| --- | --- | --- |
| `GET /movies` | `HttpGetMovies` | `query`, `offset`, `limit` |
| `GET /authors` | `HttpGetAuthors` | `query`, `offset`, `limit` |
-| `GET /reviews` | `HttpGetReviews` | `movieId`, `authorId`, `offset`, `limit` |
+| `GET /reviews` | `HttpGetReviews` | `movieId`, `authorId`, `query`, `minStars`, `offset`, `limit` |
```bash
-curl "${API_URL}movies?query=global&limit=2"
+curl "${API_URL}movies?query=space&limit=2"
```
| Status | When |
diff --git a/src/content/docs/mcp/explanation/decisions.mdx b/src/content/docs/mcp/explanation/decisions.mdx
index a305e61..e53a1b9 100644
--- a/src/content/docs/mcp/explanation/decisions.mdx
+++ b/src/content/docs/mcp/explanation/decisions.mdx
@@ -29,7 +29,7 @@ Every tool returns its result as text and as `structuredContent`, with an output
## Resources, prompts and completions too
-Tools are what most servers stop at. This one also exposes every movie, author and review as a resource, ships three prompts that embed the data they need, and completes titles and names as the user types them. It's there to show the shape of each, so you don't have to work out where they go.
+Tools are what most servers stop at. This one also exposes every movie, author and review as a resource, links to them from tool results, ships five prompts that embed the data they need, and completes titles, names and ids as the user types them. It's there to show the shape of each, so you don't have to work out where they go.
## Instructions for the model
diff --git a/src/content/docs/mcp/getting-started.mdx b/src/content/docs/mcp/getting-started.mdx
index 3bd0d7e..2e9e96e 100644
--- a/src/content/docs/mcp/getting-started.mdx
+++ b/src/content/docs/mcp/getting-started.mdx
@@ -58,24 +58,25 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
5. Call a tool. The server speaks Streamable HTTP at `/mcp`, and because it's stateless you can send a single JSON-RPC request with nothing before it.
```bash
- curl -X POST "http://localhost:5032/mcp" \
+ curl -X POST "http://localhost:8080/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
- -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_search","arguments":{"query":"global","limit":2}}}'
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_search","arguments":{"query":"space","limit":2}}}'
```
- The answer arrives as a server-sent event. Inside it, the tool's result is there twice: once as text for models that read, once as `structuredContent` for clients that parse.
+ The answer arrives as a server-sent event. Inside it, the tool's result is there twice: once as text for models that read, once as `structuredContent` for clients that parse. A `resource_link` for each movie follows the text, so a client can open it as a resource.
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4, "reviews": null },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3, "reviews": null }
],
- "totalCount": 2
+ "totalCount": 4,
+ "nextOffset": 2
}
```
- Yes, the movies are named by a corporate buzzword generator. *Assimilated global infrastructure* only got one star. Critics can be harsh.
+ The sample movies are all made up. *Deep Space Gardeners* averages 3.4 stars from five reviews, which seems fair for a film about weeding in zero gravity.
6. Or skip curl and use the Inspector. Docker Compose starts it already pointed at the server.
diff --git a/src/content/docs/mcp/guides/add-a-tool.mdx b/src/content/docs/mcp/guides/add-a-tool.mdx
index 054172e..0ca6da0 100644
--- a/src/content/docs/mcp/guides/add-a-tool.mdx
+++ b/src/content/docs/mcp/guides/add-a-tool.mdx
@@ -89,7 +89,7 @@ This guide adds `authors_get`, which returns one author with every review they'v
```
```bash
- curl -X POST "http://localhost:5032/mcp" \
+ curl -X POST "http://localhost:8080/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"authors_get","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5"}}}'
```
@@ -98,16 +98,16 @@ This guide adds `authors_get`, which returns one author with every review they'v
{
"id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5",
"name": "Jana Auer",
- "reviewCount": 10,
- "averageStars": 2.6,
+ "reviewCount": 9,
+ "averageStars": 3.3,
"reviews": [
- { "id": "0c13d0af-31ab-1705-228a-619b147ed110", "stars": 2, "movieId": "aea49d3b-0e68-0098-d9c3-70c93ea9e900", "movieTitle": "Persevering non-volatile middleware", "authorId": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "authorName": "Jana Auer" },
- { "id": "19bbc1d8-960a-df45-df54-46ad502c3013", "stars": 2, "movieId": "ecff0774-e2f7-f169-49c9-a6820ae88b30", "movieTitle": "Customizable needs-based open architecture", "authorId": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "authorName": "Jana Auer" }
+ { "id": "03ca222d-2de3-5eef-ec0b-d3915b23c4de", "stars": 2, "movieId": "c68c95ac-b7fa-4134-761c-0014588bfa01", "movieTitle": "The Courtroom Cockatoo", "authorId": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "authorName": "Jana Auer", "text": "Twenty minutes too long, in a film that needed to be an hour shorter." },
+ { "id": "0ac92e89-50e3-543b-49d7-13ee4064b32e", "stars": 4, "movieId": "c510b73e-4967-949a-7098-8263bb7b8d39", "movieTitle": "Midnight at the Laundromat", "authorId": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "authorName": "Jana Auer", "text": "It takes a while to warm up, then it really goes." }
]
}
```
- Jana averages 2.6 stars across ten reviews. Jana is not easily impressed.
+ Jana averages 3.3 stars across nine reviews, and sat through *Apprentice to a Retired Dragon* out of spite.
diff --git a/src/content/docs/mcp/guides/connect-a-client.mdx b/src/content/docs/mcp/guides/connect-a-client.mdx
index e876623..9cfdfef 100644
--- a/src/content/docs/mcp/guides/connect-a-client.mdx
+++ b/src/content/docs/mcp/guides/connect-a-client.mdx
@@ -7,12 +7,14 @@ sidebar:
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
-The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports), the current remote transport in the MCP spec, so any client that supports remote servers can connect to it. Start the server with `dotnet run --project ./src/Presentation` and give the client `http://localhost:5032/mcp`. With Docker Compose the address is `http://localhost:8080/mcp`.
+The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports), the current remote transport in the MCP spec, so any client that supports remote servers can connect to it. Start the server with `dotnet run --project ./src/Presentation` or Docker Compose, and give the client `http://localhost:8080/mcp`. Both listen there.
+
+No client to hand? Docker Compose can start one for you: see [Chat with it](../run-locally/#chat-with-it).
```bash
- claude mcp add --transport http acme-movies http://localhost:5032/mcp
+ claude mcp add --transport http acme-movies http://localhost:8080/mcp
```
Then ask it something like "which movie has the best reviews?" and watch it call `movies_search` and `movies_get` on its own.
@@ -26,7 +28,7 @@ The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specificatio
```json
{
"servers": {
- "acme-movies": { "type": "http", "url": "http://localhost:5032/mcp" }
+ "acme-movies": { "type": "http", "url": "http://localhost:8080/mcp" }
}
}
```
@@ -37,7 +39,7 @@ The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specificatio
```json
{
"mcpServers": {
- "acme-movies": { "url": "http://localhost:5032/mcp" }
+ "acme-movies": { "url": "http://localhost:8080/mcp" }
}
}
```
@@ -49,9 +51,9 @@ The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specificatio
using ModelContextProtocol.Client;
await using var client = await McpClient.CreateAsync(
- new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("http://localhost:5032/mcp") }));
+ new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("http://localhost:8080/mcp") }));
- var result = await client.CallToolAsync("movies_search", new Dictionary { ["query"] = "global" });
+ var result = await client.CallToolAsync("movies_search", new Dictionary { ["query"] = "space" });
```
@@ -60,7 +62,7 @@ The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specificatio
On `initialize` the server introduces itself with a name, title, version, description, icon and website, and a block of instructions for the model:
-> This server holds a catalogue of movies, the authors who review them, and their reviews. A review links one author to one movie with a star rating from 1 (worst) to 5 (best). Find things by name first: movies_search and authors_search return ids, review counts and average stars, and reviews_list filters reviews by movie or author. Every search returns one page at a time, so pass nextOffset back as the offset to continue rather than raising the limit. reviews_write creates a review, or changes the stars of one when you pass its reviewId. A review's movie and author can't change. reviews_delete can't be undone, so confirm with the user before calling it. Each movie, author and review can also be read as a resource at movies://\{id\}, authors://\{id\} or reviews://\{id\}, and server://version gives this server's version.
+> This server holds a catalogue of movies, the authors who review them, and their reviews. A review links one author to one movie with a star rating from 1 (worst) to 5 (best). Find things by name first: movies_search and authors_search return ids, review counts and average stars, and reviews_list filters reviews by movie or author. Every search returns one page at a time, so pass nextOffset back as the offset to continue rather than raising the limit. reviews_write creates a review, or changes the stars of one when you pass its reviewId. A review's movie and author can't change. reviews_delete can't be undone, so confirm with the user before calling it. Each movie, author and review can also be read as a resource at movies://\{id\}, authors://\{id\} or reviews://\{id\}. movies://top-rated lists the ten best rated movies, and server://version gives this server's version.
Those instructions are in `WebApplicationBuilderExtensions.cs`, and they're worth keeping up to date when you add a tool. A model that knows the shape of the data makes fewer calls and better ones.
diff --git a/src/content/docs/mcp/guides/run-locally.mdx b/src/content/docs/mcp/guides/run-locally.mdx
index 6c9aef2..9eab124 100644
--- a/src/content/docs/mcp/guides/run-locally.mdx
+++ b/src/content/docs/mcp/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:5032](http://localhost:5032) and `https://localhost:7032`. The server is at `/mcp`.
+ `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`, the same port as Docker Compose. The server is at `/mcp`.
```bash
@@ -45,6 +45,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
| `/health/ready` | Answers 200 when the database can be reached |
| `/.well-known/oauth-protected-resource/mcp` | Protected resource metadata, only when [authentication is on](../secure-the-server/) |
| [http://localhost:6274](http://localhost:6274) | The MCP Inspector, when running with Docker Compose |
+| [http://localhost:3000](http://localhost:3000) | Open WebUI, when running with Docker Compose and the `demo` profile |
| [http://localhost:18888](http://localhost:18888) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
The health endpoints answer without a token even when authentication is on, so a load balancer never needs to log in.
@@ -53,11 +54,21 @@ The health endpoints answer without a token even when authentication is on, so a
The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is the official test client, and Docker Compose runs it already pointed at `http://api:8080/mcp`. Open it, press **Connect**, and:
-- **Tools** lists every tool with its description, annotations and input schema, and gives you a form to call it.
-- **Resources** lets you read `server://version` or any `movies://`, `authors://` or `reviews://` URI.
-- **Prompts** renders each prompt with its arguments, and the title and name arguments autocomplete from the database.
+- **Tools** lists every tool with its description, annotations and input schema, and gives you a form to call it. A result lists its movies, authors and reviews under **Resource Links**, and expanding one reads it.
+- **Resources** lets you read `movies://top-rated`, `server://version` or any `movies://`, `authors://` or `reviews://` URI, and the ids autocomplete as you type an id or a title.
+- **Prompts** renders each prompt with its arguments, and the title, name and id arguments autocomplete from the database.
-Running with `dotnet run` instead? Start the Inspector yourself with `npx @modelcontextprotocol/inspector` and point it at `http://localhost:5032/mcp`.
+Running with `dotnet run` instead? Start the Inspector yourself with `npx @modelcontextprotocol/inspector` and point it at `http://localhost:8080/mcp`.
+
+## Chat with it
+
+Want to ask for things in plain English? The optional `demo` profile adds [Open WebUI](https://github.com/open-webui/open-webui), already connected to the server.
+
+```bash
+docker compose --profile demo up -d --build
+```
+
+Open [http://localhost:3000](http://localhost:3000), dismiss the release notes and ask it to list the movies. It uses Kilo's free gateway as the model, so there's no account or key, but it only works online, and your prompts and tool results go to Kilo, a third party. Keep it to sample data. Solutions created with `--include-auth` leave it out, because Open WebUI has no token to send. Stop it with `docker compose --profile demo down`.
## See the telemetry
@@ -76,17 +87,17 @@ The server is stateless, so a single `tools/call` works without an `initialize`
```bash
# One movie, with every review
-curl -X POST "http://localhost:5032/mcp" \
+curl -X POST "http://localhost:8080/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
- -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_get","arguments":{"movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593"}}}'
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_get","arguments":{"movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28"}}}'
# Write a review
-curl -X POST "http://localhost:5032/mcp" \
+curl -X POST "http://localhost:8080/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
- -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"reviews_write","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","stars":5}}}'
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"reviews_write","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}}}'
# Read a resource
-curl -X POST "http://localhost:5032/mcp" \
+curl -X POST "http://localhost:8080/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"server://version"}}'
```
@@ -125,5 +136,5 @@ docker build . --target run --tag acme-movies:latest \
The `server://version` resource and the `serverInfo` in the initialize response then report `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/mcp/guides/secure-the-server.mdx b/src/content/docs/mcp/guides/secure-the-server.mdx
index ab801d9..dd49da8 100644
--- a/src/content/docs/mcp/guides/secure-the-server.mdx
+++ b/src/content/docs/mcp/guides/secure-the-server.mdx
@@ -42,7 +42,7 @@ Out of the box the server runs wide open, and there's no authentication code in
3. Get a token from the authority for that audience, and send it.
```bash
- curl -X POST "http://localhost:5032/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
+ curl -X POST "http://localhost:8080/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
diff --git a/src/content/docs/mcp/index.mdx b/src/content/docs/mcp/index.mdx
index ebd491d..197c6b6 100644
--- a/src/content/docs/mcp/index.mdx
+++ b/src/content/docs/mcp/index.mdx
@@ -27,7 +27,7 @@ import Family from '../../../components/docs/Family.astro';
Six tools named for the jobs an agent actually does, like `movies_search` and `reviews_write`, with read-only, destructive and idempotent hints.
- Resources for every movie, author and review, plus prompts with completions that suggest titles and names as you type.
+ Resources for every movie, author and review, linked from tool results, plus prompts with completions that suggest titles, names and ids as you type.
Validation lives in the Application layer with FluentValidation, and tool errors tell the agent how to recover instead of just giving up.
diff --git a/src/content/docs/mcp/reference/configuration.mdx b/src/content/docs/mcp/reference/configuration.mdx
index 0e316bc..71946eb 100644
--- a/src/content/docs/mcp/reference/configuration.mdx
+++ b/src/content/docs/mcp/reference/configuration.mdx
@@ -57,4 +57,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:5032` and `https://localhost:7032`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8080` and `https://localhost:8081`.
diff --git a/src/content/docs/mcp/reference/resources-and-prompts.mdx b/src/content/docs/mcp/reference/resources-and-prompts.mdx
index bf93fe1..f0b7b02 100644
--- a/src/content/docs/mcp/reference/resources-and-prompts.mdx
+++ b/src/content/docs/mcp/reference/resources-and-prompts.mdx
@@ -5,18 +5,19 @@ sidebar:
order: 2
---
-Resources live in `src/Presentation/Resources`, prompts in `src/Presentation/Prompts`, and the completion handler in `src/Presentation/Completions`. All of them need a token when [authentication is on](../../guides/secure-the-server/).
+Resources live in `src/Presentation/Resources`, prompts in `src/Presentation/Prompts`, and the completion handler in `src/Presentation/Completions`. In a solution created with `--include-auth`, all of them need a token. Every resource and prompt has an icon, a movie, an author, a review or the server, from the [brand repository](https://github.com/LeanMeanCleanApps/brand).
## Resources
-Every resource returns `application/json`. The movie, author and review resources are templates, so they're listed under `resources/templates/list`; the version resource is a plain resource under `resources/list`.
+Every resource returns `application/json`. The movie, author and review resources are templates, so they're listed under `resources/templates/list`; the top rated and version resources are plain resources under `resources/list`.
| URI | Name | Returns |
| --- | --- | --- |
+| `movies://top-rated` | `top_rated_movies` | A [page](../tools/#pages) of the ten best rated [movies](../tools/#movie), highest average stars first |
| `movies://{movieId}` | `movie` | A [movie](../tools/#movie) with every review |
| `authors://{authorId}` | `author` | An [author](../tools/#author) with every review |
| `reviews://{reviewId}` | `review` | A [review](../tools/#review) |
-| `server://version` | `version` | The version stamped into the build |
+| `server://version` | `server_version` | The version stamped into the build |
```json
{ "fileVersion": "1.1.0.0", "informationalVersion": "1.1.0.0+c7e22dee4ffd508b82caca702ebb713d0a2ba8bc" }
@@ -29,15 +30,25 @@ An unknown id fails with the JSON-RPC `-32002` resource not found error, and an
| Prompt | Arguments | What it asks the model to do |
| --- | --- | --- |
| `summarise_movie_reception` | `movieTitle` | Summarise a movie's average stars, how its ratings spread, and who liked it most and least |
+| `summarise_movie_reviews` | `movieId` | Summarise what reviewers said about a movie: the themes they agree on, where they disagree, and whether their words match their stars |
| `compare_movies` | `firstMovieTitle`, `secondMovieTitle` | Compare how reviewers rated two movies and say which they preferred |
-| `review_movie` | `movieTitle`, `authorName` | Find the movie and the author, ask for a star rating, and save the review with `reviews_write` |
+| `summarise_author_reviews` | `authorName` | Summarise an author's reviewing habits: how many reviews, how generous they are, and the movies they rated highest and lowest |
+| `review_movie` | `movieTitle`, `authorName` | Find the movie and the author, ask for a star rating and what they thought, and save the review with `reviews_write` |
-The first two look the movie up by title. When they find exactly one match, the returned messages embed the movie as a `movies://` resource, reviews and all, so the model has the data without another call. When they don't, the messages tell the model which tools to use to find it.
+`summarise_movie_reception` and `compare_movies` look the movie up by title, and `summarise_author_reviews` looks the author up by name. When they find it, by an exact match or as the only match, the returned messages embed it as a `movies://` or `authors://` resource, reviews and all, so the model has the data without another call. When they don't, the messages tell the model which tools to use to find it. `summarise_movie_reviews` takes the movie's id, so it always embeds the movie, or fails with the JSON-RPC `-32002` error when there's no movie with that id.
## Completions
-The `movieTitle`, `firstMovieTitle` and `secondMovieTitle` arguments complete from movie titles, and `authorName` completes from author names, up to 50 at a time with the total count. A value over 100 characters, or an argument the handler doesn't know, completes to nothing.
+The `movieTitle`, `firstMovieTitle` and `secondMovieTitle` arguments complete from movie titles, and `authorName` completes from author names, up to 50 at a time with the total count.
```json
-{ "completion": { "values": ["Assimilated global infrastructure", "Automated global alliance"], "total": 2, "hasMore": false } }
+{ "completion": { "values": ["Deep Space Gardeners", "Postcards from Outer Space", "Space Station Bake Sale", "The Last Space Lighthouse"], "total": 4, "hasMore": false } }
```
+
+Ids complete too: the `movieId` argument of `summarise_movie_reviews`, and the ids in the `movies://{movieId}`, `authors://{authorId}` and `reviews://{reviewId}` templates. Type the start of an id, or part of the movie's title or the author's name, and you get the matching ids. A review is found by its movie's title.
+
+```json
+{ "completion": { "values": ["cb188ef6-14e7-f85f-2f78-924c2ab26b28", "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "ec850910-9692-cf5d-8ce4-938b304e9228", "ec68dc8c-2c08-5139-b68f-6c4be5061bdb"], "total": 4, "hasMore": false } }
+```
+
+A value over 100 characters, or an argument the handler doesn't know, completes to nothing.
diff --git a/src/content/docs/mcp/reference/tools.mdx b/src/content/docs/mcp/reference/tools.mdx
index e5b4966..630a472 100644
--- a/src/content/docs/mcp/reference/tools.mdx
+++ b/src/content/docs/mcp/reference/tools.mdx
@@ -5,7 +5,7 @@ sidebar:
order: 1
---
-Every tool lives in `src/Presentation/Tools`, one static class per feature. Arguments are JSON, named in camelCase. Every tool returns its result twice: as a text content block holding JSON, for models that read, and as `structuredContent`, for clients that parse, with an output schema in the tool's listing. When [authentication is on](../../guides/secure-the-server/), every tool needs a token.
+Every tool lives in `src/Presentation/Tools`, one static class per feature. Arguments are JSON, named in camelCase. Every tool returns its result twice: as a text content block holding JSON, for models that read, and as `structuredContent`, for clients that parse, with an output schema in the tool's listing. After the text comes a `resource_link` block for each movie, author or review in the result, pointing at its `movies://`, `authors://` or `reviews://` [resource](../resources-and-prompts/), so a client can open it. In a solution created with `--include-auth`, every tool needs a token.
| Tool | Title | Read-only | Destructive | Idempotent |
| --- | --- | --- | --- | --- |
@@ -16,7 +16,7 @@ Every tool lives in `src/Presentation/Tools`, one static class per feature. Argu
| [`reviews_write`](#reviews_write) | Write review | | yes | |
| [`reviews_delete`](#reviews_delete) | Delete review | | yes | yes |
-Every tool has `openWorldHint` false: they only ever touch this server's own database.
+Every tool has `openWorldHint` false: they only ever touch this server's own database. Every tool also has an icon, a movie, an author or a review, from the [brand repository](https://github.com/LeanMeanCleanApps/brand).
## movies_search
@@ -34,10 +34,11 @@ Returns a [page](#pages) of [movies](#movie).
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4, "reviews": null },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3, "reviews": null }
],
- "totalCount": 2
+ "totalCount": 4,
+ "nextOffset": 2
}
```
@@ -80,7 +81,7 @@ Returns a [page](#pages) of [reviews](#review).
## reviews_write
-Creates a review of a movie by an author, or changes the stars of an existing review when `reviewId` is passed. A review's movie and author can't change.
+Creates a review of a movie by an author, with optional text, or changes the stars and text of an existing review when `reviewId` is passed. A review's movie and author can't change.
| Argument | Type | Rules |
| --- | --- | --- |
@@ -88,6 +89,7 @@ Creates a review of a movie by an author, or changes the stars of an existing re
| `movieId` | guid | Needed to create a review. An existing movie |
| `authorId` | guid | Needed to create a review. An existing author |
| `reviewId` | guid | Leave empty to create. An existing review to change |
+| `text` | string | Optional. Up to 1000 characters. Leave it empty when changing a review to keep its text |
Returns the saved [review](#review).
@@ -103,6 +105,8 @@ Returns `{ "reviewId": "...", "deleted": true }`.
## Results
+Properties with no value are written as `null`, not left out, so every result matches its output schema. The schema describes them with `anyOf` and a `null` type, which clients that only understand one type per property accept too.
+
### Pages
Every search and list tool returns one page.
@@ -111,7 +115,7 @@ Every search and list tool returns one page.
| --- | --- | --- |
| `items` | array | The results on this page |
| `totalCount` | integer | The number of results across every page |
-| `nextOffset` | integer | Pass it as `offset` to get the next page. Left out on the last page |
+| `nextOffset` | integer | Pass it as `offset` to get the next page. Null on the last page |
### Movie
@@ -120,8 +124,8 @@ Every search and list tool returns one page.
| `id` | guid | The movie's id |
| `title` | string | The movie's title |
| `reviewCount` | integer | How many reviews the movie has |
-| `averageStars` | number | The average rating to one decimal place. Left out when there are no reviews |
-| `reviews` | array of [review](#review) | Every review of the movie. Only when asked for |
+| `averageStars` | number | The average rating to one decimal place. Null when there are no reviews |
+| `reviews` | array of [review](#review) | Every review of the movie. Null unless asked for |
### Author
@@ -130,8 +134,8 @@ Every search and list tool returns one page.
| `id` | guid | The author's id |
| `name` | string | The author's full name |
| `reviewCount` | integer | How many reviews the author has written |
-| `averageStars` | number | The average rating they give. Left out when there are no reviews |
-| `reviews` | array of [review](#review) | Every review by the author. Only when asked for |
+| `averageStars` | number | The average rating they give. Null when there are no reviews |
+| `reviews` | array of [review](#review) | Every review by the author. Null unless asked for |
### Review
@@ -143,6 +147,7 @@ Every search and list tool returns one page.
| `movieTitle` | string | The movie's title |
| `authorId` | guid | The author's id |
| `authorName` | string | The author's full name |
+| `text` | string | What the author said about the movie, or null when there's no text |
## Errors
diff --git a/src/content/docs/minimalapi/getting-started.mdx b/src/content/docs/minimalapi/getting-started.mdx
index 6484523..8de91fe 100644
--- a/src/content/docs/minimalapi/getting-started.mdx
+++ b/src/content/docs/minimalapi/getting-started.mdx
@@ -55,12 +55,12 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
-5. Open the Scalar API reference at [http://localhost:5032/scalar](http://localhost:5032/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
+5. Open the Scalar API reference at [http://localhost:8080/scalar](http://localhost:8080/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
6. Or call it from the terminal.
```bash
- curl "http://localhost:5032/api/movies?query=global&limit=2"
+ curl "http://localhost:8080/api/movies?query=space&limit=2"
```
You get a page of movies back:
@@ -68,16 +68,16 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
],
"offset": 0,
"limit": 2,
- "totalCount": 2
+ "totalCount": 4
}
```
- Yes, the movies are named by a corporate buzzword generator. *Assimilated global infrastructure* only got one star. Critics can be harsh.
+ The sample movies are all made up. *Deep Space Gardeners* averages 3.4 stars from five reviews, which seems fair for a film about weeding in zero gravity.
7. Run the tests while you're here.
diff --git a/src/content/docs/minimalapi/guides/add-an-endpoint.mdx b/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
index 9dcebb8..3dc998e 100644
--- a/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
+++ b/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
@@ -101,22 +101,22 @@ This guide adds `GET /api/movies/{id}/reviews`, one page of the reviews written
```bash
dotnet test
dotnet run --project ./src/Presentation
- curl "http://localhost:5032/api/movies/e159975e-4e6a-1ffd-713e-1eb9d8d4a593/reviews?limit=2"
+ curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
```
```json
{
"items": [
- { "id": "969cb90c-95ae-9189-1e52-0b38d34a911b", "stars": 4, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } },
- { "id": "a5ebd747-0408-0a96-1af1-eb5cb93bc814", "stars": 1, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } }
+ { "id": "45911e6f-a366-417b-1453-f34097338ee2", "stars": 5, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "Everyone involved should be very proud." },
+ { "id": "8832846c-4100-e92a-0b66-e59dcfde2acc", "stars": 3, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "I laughed twice and looked at my phone once." }
],
"offset": 0,
"limit": 2,
- "totalCount": 6
+ "totalCount": 5
}
```
- Caroline gave the same movie a 4 and a 1. Sample data has no continuity editor.
+ Caroline gave the same movie a 5 and a 3. Sample data has no continuity editor.
diff --git a/src/content/docs/minimalapi/guides/run-locally.mdx b/src/content/docs/minimalapi/guides/run-locally.mdx
index 90bf1f3..2707e7e 100644
--- a/src/content/docs/minimalapi/guides/run-locally.mdx
+++ b/src/content/docs/minimalapi/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:5032](http://localhost:5032) and `https://localhost:7032`.
+ `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`, the same port as Docker Compose.
```bash
@@ -66,14 +66,14 @@ The sample data uses a fixed seed, so these ids are the same on every machine. S
```bash
# Search movies, two at a time
-curl "http://localhost:5032/api/movies?query=global&limit=2"
+curl "http://localhost:8080/api/movies?query=space&limit=2"
# One movie, with its reviews
-curl "http://localhost:5032/api/movies/e159975e-4e6a-1ffd-713e-1eb9d8d4a593"
+curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
# Write a review
-curl -X POST "http://localhost:5032/api/reviews" -H "Content-Type: application/json" \
- -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","stars":5}'
+curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
+ -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
## Run the tests
@@ -110,5 +110,5 @@ docker build . --target run --tag acme-movies:latest \
`GET /api/version` then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/minimalapi/guides/secure-the-api.mdx b/src/content/docs/minimalapi/guides/secure-the-api.mdx
index eeb0121..ce5fac6 100644
--- a/src/content/docs/minimalapi/guides/secure-the-api.mdx
+++ b/src/content/docs/minimalapi/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl "http://localhost:5032/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
+ curl "http://localhost:8080/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
```
Without a token, or with one from the wrong issuer or for the wrong audience, the API answers `401 Unauthorized`.
diff --git a/src/content/docs/minimalapi/reference/configuration.mdx b/src/content/docs/minimalapi/reference/configuration.mdx
index 1ce02e7..863ca90 100644
--- a/src/content/docs/minimalapi/reference/configuration.mdx
+++ b/src/content/docs/minimalapi/reference/configuration.mdx
@@ -55,4 +55,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:5032` and `https://localhost:7032`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8080` and `https://localhost:8081`.
diff --git a/src/content/docs/minimalapi/reference/endpoints.mdx b/src/content/docs/minimalapi/reference/endpoints.mdx
index 231728a..325013c 100644
--- a/src/content/docs/minimalapi/reference/endpoints.mdx
+++ b/src/content/docs/minimalapi/reference/endpoints.mdx
@@ -5,7 +5,7 @@ sidebar:
order: 1
---
-Every endpoint lives in `src/Presentation/Endpoints`, one static class per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. Every endpoint requires authorization, which only has an effect when [authentication is on](../../guides/secure-the-api/).
+Every endpoint lives in `src/Presentation/Endpoints`, one static class per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. In a solution created with `--include-auth`, every endpoint except health needs a token (see [Secure the API](../../guides/secure-the-api/)).
The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it.
@@ -35,18 +35,18 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:5032/api/movies?query=global&limit=2"
+curl "http://localhost:8080/api/movies?query=space&limit=2"
```
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
],
"offset": 0,
"limit": 2,
- "totalCount": 2
+ "totalCount": 4
}
```
@@ -63,8 +63,8 @@ Lists authors ordered by last name then first name, one page at a time, optional
```json
{
"items": [
- { "id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "firstName": "Jana", "lastName": "Auer", "reviewCount": 10, "averageStars": 2.6 },
- { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 12, "averageStars": 2.7 }
+ { "id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "firstName": "Jana", "lastName": "Auer", "reviewCount": 9, "averageStars": 3.3 },
+ { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 10, "averageStars": 3.2 }
],
"offset": 0,
"limit": 2,
@@ -80,24 +80,42 @@ Returns one [author](#author) with every review they have written, or `404` when
### GET /api/reviews
-Lists reviews ordered by movie then author, one page at a time, optionally only those of one movie, by one author, or both.
+Lists reviews ordered by movie then author, one page at a time. Filter by movie, by author, by words in the review text and by a minimum star rating, in any combination.
| Query parameter | Type | Default | Rules |
| --- | --- | --- | --- |
| `movieId` | guid | none | Only reviews of this movie |
| `authorId` | guid | none | Only reviews by this author |
+| `query` | string | none | Up to 100 characters. Matches the review text, ignoring case |
+| `minStars` | integer | none | 1 to 5. Only reviews with at least this many stars |
| `offset` | integer | `0` | 0 or more |
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:5032/api/reviews?movieId=e159975e-4e6a-1ffd-713e-1eb9d8d4a593&limit=2"
+curl "http://localhost:8080/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
```
```json
{
"items": [
- { "id": "969cb90c-95ae-9189-1e52-0b38d34a911b", "stars": 4, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } },
- { "id": "a5ebd747-0408-0a96-1af1-eb5cb93bc814", "stars": 1, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } }
+ { "id": "45911e6f-a366-417b-1453-f34097338ee2", "stars": 5, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "Everyone involved should be very proud." },
+ { "id": "8832846c-4100-e92a-0b66-e59dcfde2acc", "stars": 3, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "I laughed twice and looked at my phone once." }
+ ],
+ "offset": 0,
+ "limit": 2,
+ "totalCount": 5
+}
+```
+
+```bash
+curl "http://localhost:8080/api/reviews?query=funny&minStars=4&limit=2"
+```
+
+```json
+{
+ "items": [
+ { "id": "85196da9-b404-4905-a1f8-9e0dc88f67e4", "stars": 5, "reviewedMovie": { "id": "33b07882-f520-65ee-5997-b1024edd80b5", "title": "A Quiet Murder in Wagga Wagga" }, "reviewAuthor": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "firstName": "Martha", "lastName": "Nicolas" }, "text": "Funny, sad and completely gripping." },
+ { "id": "625d83d4-587f-f872-f5b5-7f923a37856f", "stars": 4, "reviewedMovie": { "id": "ac6e5917-e549-db65-a0b7-aff1fc817570", "title": "Dragon of the Salt Flats" }, "reviewAuthor": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "firstName": "Martha", "lastName": "Nicolas" }, "text": "Funny, warm and better than it needed to be." }
],
"offset": 0,
"limit": 2,
@@ -118,21 +136,23 @@ Creates a review. The author and the movie must already exist.
| `authorId` | guid | Required. An existing author |
| `movieId` | guid | Required. An existing movie |
| `stars` | integer | 1 to 5 |
+| `text` | string | Optional. Up to 1000 characters |
```bash
-curl -X POST "http://localhost:5032/api/reviews" -H "Content-Type: application/json" \
- -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","stars":5}'
+curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
+ -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
Answers `201 Created` with the new [review](#review) in the body and its URL in the `Location` header. An unknown author or movie answers `404`.
### PUT /api/reviews/\{id\}
-Changes the star rating of a review. The author and the movie of a review can't change.
+Changes the star rating of a review, and its text when there's new text. The author and the movie of a review can't change.
| Body property | Type | Rules |
| --- | --- | --- |
| `stars` | integer | 1 to 5 |
+| `text` | string | Optional. Up to 1000 characters. Leave it out to keep the text the review has |
Answers `204 No Content`, or `404` when there's no review with that id.
@@ -144,7 +164,7 @@ Permanently removes a review. Answers `204 No Content`, or `404` when there's no
### GET /api/version
-Returns the [version](#version) stamped into the build. It needs a token when authentication is on, like everything else; the version is also on every span, metric and log as `service.version`.
+Returns the [version](#version) stamped into the build. With `--include-auth` it needs a token, like everything else; the version is also on every span, metric and log as `service.version`.
```json
{ "fileVersion": "1.1.0.0", "informationalVersion": "1.1.0.0+044d2922e197f24d9a3f311f87a7e323add1a530" }
@@ -209,6 +229,7 @@ To read everything, start at `offset` 0 and add `limit` to it until `offset` rea
| `stars` | integer | The rating, from 1 to 5 |
| `reviewedMovie` | object | The movie's `id` and `title` |
| `reviewAuthor` | object | The author's `id`, `firstName` and `lastName` |
+| `text` | string | What the author said about the movie. Left out when there's no text |
### Version
diff --git a/src/content/docs/webapi/getting-started.mdx b/src/content/docs/webapi/getting-started.mdx
index 59522f7..52aacd0 100644
--- a/src/content/docs/webapi/getting-started.mdx
+++ b/src/content/docs/webapi/getting-started.mdx
@@ -60,7 +60,7 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
6. Or call it from the terminal.
```bash
- curl "http://localhost:8080/api/movies?query=global&limit=2"
+ curl "http://localhost:8080/api/movies?query=space&limit=2"
```
You get a page of movies back:
@@ -68,16 +68,16 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
],
"offset": 0,
"limit": 2,
- "totalCount": 2
+ "totalCount": 4
}
```
- Yes, the movies are named by a corporate buzzword generator. *Assimilated global infrastructure* only got one star. Critics can be harsh.
+ The sample movies are all made up. *Deep Space Gardeners* averages 3.4 stars from five reviews, which seems fair for a film about weeding in zero gravity.
7. Run the tests while you're here.
diff --git a/src/content/docs/webapi/guides/add-an-endpoint.mdx b/src/content/docs/webapi/guides/add-an-endpoint.mdx
index e19f29d..957173b 100644
--- a/src/content/docs/webapi/guides/add-an-endpoint.mdx
+++ b/src/content/docs/webapi/guides/add-an-endpoint.mdx
@@ -95,22 +95,22 @@ This guide adds `GET /api/movies/{id}/reviews`, one page of the reviews written
```bash
dotnet test
dotnet run --project ./src/Presentation
- curl "http://localhost:8080/api/movies/e159975e-4e6a-1ffd-713e-1eb9d8d4a593/reviews?limit=2"
+ curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
```
```json
{
"items": [
- { "id": "969cb90c-95ae-9189-1e52-0b38d34a911b", "stars": 4, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } },
- { "id": "a5ebd747-0408-0a96-1af1-eb5cb93bc814", "stars": 1, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } }
+ { "id": "45911e6f-a366-417b-1453-f34097338ee2", "stars": 5, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "Everyone involved should be very proud." },
+ { "id": "8832846c-4100-e92a-0b66-e59dcfde2acc", "stars": 3, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "I laughed twice and looked at my phone once." }
],
"offset": 0,
"limit": 2,
- "totalCount": 6
+ "totalCount": 5
}
```
- Caroline gave the same movie a 4 and a 1. Sample data has no continuity editor.
+ Caroline gave the same movie a 5 and a 3. Sample data has no continuity editor.
diff --git a/src/content/docs/webapi/guides/run-locally.mdx b/src/content/docs/webapi/guides/run-locally.mdx
index 3d8c023..abb95a4 100644
--- a/src/content/docs/webapi/guides/run-locally.mdx
+++ b/src/content/docs/webapi/guides/run-locally.mdx
@@ -66,14 +66,14 @@ The sample data uses a fixed seed, so these ids are the same on every machine. S
```bash
# Search movies, two at a time
-curl "http://localhost:8080/api/movies?query=global&limit=2"
+curl "http://localhost:8080/api/movies?query=space&limit=2"
# One movie, with its reviews
-curl "http://localhost:8080/api/movies/e159975e-4e6a-1ffd-713e-1eb9d8d4a593"
+curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
# Write a review
curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
- -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","stars":5}'
+ -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
## Run the tests
diff --git a/src/content/docs/webapi/reference/endpoints.mdx b/src/content/docs/webapi/reference/endpoints.mdx
index 86dbcd1..87d3b5f 100644
--- a/src/content/docs/webapi/reference/endpoints.mdx
+++ b/src/content/docs/webapi/reference/endpoints.mdx
@@ -5,7 +5,7 @@ sidebar:
order: 1
---
-Every endpoint lives in `src/Presentation/Controllers`, one controller per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. Every endpoint requires authorization, which only has an effect when [authentication is on](../../guides/secure-the-api/).
+Every endpoint lives in `src/Presentation/Controllers`, one controller per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. In a solution created with `--include-auth`, every endpoint except health needs a token (see [Secure the API](../../guides/secure-the-api/)).
The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it.
@@ -35,18 +35,18 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:8080/api/movies?query=global&limit=2"
+curl "http://localhost:8080/api/movies?query=space&limit=2"
```
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
],
"offset": 0,
"limit": 2,
- "totalCount": 2
+ "totalCount": 4
}
```
@@ -63,8 +63,8 @@ Lists authors ordered by last name then first name, one page at a time, optional
```json
{
"items": [
- { "id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "firstName": "Jana", "lastName": "Auer", "reviewCount": 10, "averageStars": 2.6 },
- { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 12, "averageStars": 2.7 }
+ { "id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "firstName": "Jana", "lastName": "Auer", "reviewCount": 9, "averageStars": 3.3 },
+ { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 10, "averageStars": 3.2 }
],
"offset": 0,
"limit": 2,
@@ -80,24 +80,42 @@ Returns one [author](#author) with every review they have written, or `404` when
### GET /api/reviews
-Lists reviews ordered by movie then author, one page at a time, optionally only those of one movie, by one author, or both.
+Lists reviews ordered by movie then author, one page at a time. Filter by movie, by author, by words in the review text and by a minimum star rating, in any combination.
| Query parameter | Type | Default | Rules |
| --- | --- | --- | --- |
| `movieId` | guid | none | Only reviews of this movie |
| `authorId` | guid | none | Only reviews by this author |
+| `query` | string | none | Up to 100 characters. Matches the review text, ignoring case |
+| `minStars` | integer | none | 1 to 5. Only reviews with at least this many stars |
| `offset` | integer | `0` | 0 or more |
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:8080/api/reviews?movieId=e159975e-4e6a-1ffd-713e-1eb9d8d4a593&limit=2"
+curl "http://localhost:8080/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
```
```json
{
"items": [
- { "id": "969cb90c-95ae-9189-1e52-0b38d34a911b", "stars": 4, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } },
- { "id": "a5ebd747-0408-0a96-1af1-eb5cb93bc814", "stars": 1, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } }
+ { "id": "45911e6f-a366-417b-1453-f34097338ee2", "stars": 5, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "Everyone involved should be very proud." },
+ { "id": "8832846c-4100-e92a-0b66-e59dcfde2acc", "stars": 3, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "I laughed twice and looked at my phone once." }
+ ],
+ "offset": 0,
+ "limit": 2,
+ "totalCount": 5
+}
+```
+
+```bash
+curl "http://localhost:8080/api/reviews?query=funny&minStars=4&limit=2"
+```
+
+```json
+{
+ "items": [
+ { "id": "85196da9-b404-4905-a1f8-9e0dc88f67e4", "stars": 5, "reviewedMovie": { "id": "33b07882-f520-65ee-5997-b1024edd80b5", "title": "A Quiet Murder in Wagga Wagga" }, "reviewAuthor": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "firstName": "Martha", "lastName": "Nicolas" }, "text": "Funny, sad and completely gripping." },
+ { "id": "625d83d4-587f-f872-f5b5-7f923a37856f", "stars": 4, "reviewedMovie": { "id": "ac6e5917-e549-db65-a0b7-aff1fc817570", "title": "Dragon of the Salt Flats" }, "reviewAuthor": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "firstName": "Martha", "lastName": "Nicolas" }, "text": "Funny, warm and better than it needed to be." }
],
"offset": 0,
"limit": 2,
@@ -118,21 +136,23 @@ Creates a review. The author and the movie must already exist.
| `authorId` | guid | Required. An existing author |
| `movieId` | guid | Required. An existing movie |
| `stars` | integer | 1 to 5 |
+| `text` | string | Optional. Up to 1000 characters |
```bash
curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
- -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","stars":5}'
+ -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
Answers `201 Created` with the new [review](#review) in the body and its URL in the `Location` header. An unknown author or movie answers `404`.
### PUT /api/reviews/\{id\}
-Changes the star rating of a review. The author and the movie of a review can't change.
+Changes the star rating of a review, and its text when there's new text. The author and the movie of a review can't change.
| Body property | Type | Rules |
| --- | --- | --- |
| `stars` | integer | 1 to 5 |
+| `text` | string | Optional. Up to 1000 characters. Leave it out to keep the text the review has |
Answers `204 No Content`, or `404` when there's no review with that id.
@@ -144,7 +164,7 @@ Permanently removes a review. Answers `204 No Content`, or `404` when there's no
### GET /api/version
-Returns the [version](#version) stamped into the build. It needs a token when authentication is on, like everything else; the version is also on every span, metric and log as `service.version`.
+Returns the [version](#version) stamped into the build. With `--include-auth` it needs a token, like everything else; the version is also on every span, metric and log as `service.version`.
```json
{ "fileVersion": "1.1.0.0", "informationalVersion": "1.1.0.0+044d2922e197f24d9a3f311f87a7e323add1a530" }
@@ -209,6 +229,7 @@ To read everything, start at `offset` 0 and add `limit` to it until `offset` rea
| `stars` | integer | The rating, from 1 to 5 |
| `reviewedMovie` | object | The movie's `id` and `title` |
| `reviewAuthor` | object | The author's `id`, `firstName` and `lastName` |
+| `text` | string | What the author said about the movie. Left out when there's no text |
### Version
From c6af68df004e3373910a9d9efc66a3fbd0b2a46d Mon Sep 17 00:00:00 2001
From: Stephen Walsh <868017+stphnwlsh@users.noreply.github.com>
Date: Fri, 6 Nov 2026 23:03:40 +0000
Subject: [PATCH 2/3] Use the HTTPS address in the dotnet run examples
dotnet run redirects http://localhost:8080 to https://localhost:8081, so a
curl against the HTTP address got an empty 307. The examples use the HTTPS
address and say to trust the development certificate once. Docker Compose
only listens on HTTP, so its examples keep http://localhost:8080.
---
src/content/docs/graphql/getting-started.mdx | 6 ++++--
src/content/docs/graphql/guides/add-a-field.mdx | 2 +-
src/content/docs/graphql/guides/run-locally.mdx | 4 ++--
src/content/docs/graphql/guides/secure-the-api.mdx | 2 +-
src/content/docs/mcp/getting-started.mdx | 4 +++-
src/content/docs/mcp/guides/add-a-tool.mdx | 2 +-
src/content/docs/mcp/guides/connect-a-client.mdx | 2 +-
src/content/docs/mcp/guides/run-locally.mdx | 12 ++++++------
src/content/docs/mcp/guides/secure-the-server.mdx | 2 +-
src/content/docs/minimalapi/getting-started.mdx | 6 ++++--
.../docs/minimalapi/guides/add-an-endpoint.mdx | 2 +-
src/content/docs/minimalapi/guides/run-locally.mdx | 10 +++++-----
.../docs/minimalapi/guides/secure-the-api.mdx | 2 +-
src/content/docs/minimalapi/reference/endpoints.mdx | 10 +++++-----
src/content/docs/webapi/getting-started.mdx | 6 ++++--
src/content/docs/webapi/guides/add-an-endpoint.mdx | 2 +-
src/content/docs/webapi/guides/run-locally.mdx | 10 +++++-----
src/content/docs/webapi/guides/secure-the-api.mdx | 2 +-
src/content/docs/webapi/reference/endpoints.mdx | 10 +++++-----
19 files changed, 52 insertions(+), 44 deletions(-)
diff --git a/src/content/docs/graphql/getting-started.mdx b/src/content/docs/graphql/getting-started.mdx
index 993ed04..d5589dc 100644
--- a/src/content/docs/graphql/getting-started.mdx
+++ b/src/content/docs/graphql/getting-started.mdx
@@ -55,7 +55,9 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
-5. Open Nitro, the GraphQL IDE, at [http://localhost:8080/graphql](http://localhost:8080/graphql). The schema is on the left, with every type and field described, and the editor autocompletes as you type. Paste this in and press play:
+ It answers on `https://localhost:8081`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+
+5. Open Nitro, the GraphQL IDE, at [https://localhost:8081/graphql](https://localhost:8081/graphql). The schema is on the left, with every type and field described, and the editor autocompletes as you type. Paste this in and press play:
```graphql
{
@@ -70,7 +72,7 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
6. Or ask from the terminal.
```bash
- curl -X POST "http://localhost:8080/graphql" -H "Content-Type: application/json" \
+ curl -X POST "https://localhost:8081/graphql" -H "Content-Type: application/json" \
-d '{"query":"{ movies(query: \"space\", first: 2) { totalCount nodes { id title reviewCount averageStars } } }"}'
```
diff --git a/src/content/docs/graphql/guides/add-a-field.mdx b/src/content/docs/graphql/guides/add-a-field.mdx
index d93bb46..3910ff9 100644
--- a/src/content/docs/graphql/guides/add-a-field.mdx
+++ b/src/content/docs/graphql/guides/add-a-field.mdx
@@ -60,7 +60,7 @@ This guide adds `bestReview` to the `Movie` type: the highest rated review of th
```bash
dotnet run --project ./src/Presentation
- curl "http://localhost:8080/graphql?sdl" > tests/Presentation.Tests.Integration/GraphQL/Schema/schema.graphql
+ curl "https://localhost:8081/graphql?sdl" > tests/Presentation.Tests.Integration/GraphQL/Schema/schema.graphql
```
The diff is the one field, described:
diff --git a/src/content/docs/graphql/guides/run-locally.mdx b/src/content/docs/graphql/guides/run-locally.mdx
index b0f362b..6d44587 100644
--- a/src/content/docs/graphql/guides/run-locally.mdx
+++ b/src/content/docs/graphql/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`, the same port as Docker Compose.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8081](https://localhost:8081), and on `https://localhost:8081`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
```bash
@@ -117,5 +117,5 @@ docker build . --target run --tag acme-movies:latest \
The `version` query then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/graphql/guides/secure-the-api.mdx b/src/content/docs/graphql/guides/secure-the-api.mdx
index feadb9d..8561b56 100644
--- a/src/content/docs/graphql/guides/secure-the-api.mdx
+++ b/src/content/docs/graphql/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl -X POST "http://localhost:8080/graphql" -H "Authorization: Bearer eyJhbGciOi..." \
+ curl -X POST "https://localhost:8081/graphql" -H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" -d '{"query":"{ movies { totalCount } }"}'
```
diff --git a/src/content/docs/mcp/getting-started.mdx b/src/content/docs/mcp/getting-started.mdx
index 2e9e96e..b58d294 100644
--- a/src/content/docs/mcp/getting-started.mdx
+++ b/src/content/docs/mcp/getting-started.mdx
@@ -55,10 +55,12 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
+ It answers on `https://localhost:8081`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+
5. Call a tool. The server speaks Streamable HTTP at `/mcp`, and because it's stateless you can send a single JSON-RPC request with nothing before it.
```bash
- curl -X POST "http://localhost:8080/mcp" \
+ curl -X POST "https://localhost:8081/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_search","arguments":{"query":"space","limit":2}}}'
```
diff --git a/src/content/docs/mcp/guides/add-a-tool.mdx b/src/content/docs/mcp/guides/add-a-tool.mdx
index 0ca6da0..e79ac3d 100644
--- a/src/content/docs/mcp/guides/add-a-tool.mdx
+++ b/src/content/docs/mcp/guides/add-a-tool.mdx
@@ -89,7 +89,7 @@ This guide adds `authors_get`, which returns one author with every review they'v
```
```bash
- curl -X POST "http://localhost:8080/mcp" \
+ curl -X POST "https://localhost:8081/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"authors_get","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5"}}}'
```
diff --git a/src/content/docs/mcp/guides/connect-a-client.mdx b/src/content/docs/mcp/guides/connect-a-client.mdx
index 9cfdfef..ce93e20 100644
--- a/src/content/docs/mcp/guides/connect-a-client.mdx
+++ b/src/content/docs/mcp/guides/connect-a-client.mdx
@@ -7,7 +7,7 @@ sidebar:
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
-The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports), the current remote transport in the MCP spec, so any client that supports remote servers can connect to it. Start the server with `dotnet run --project ./src/Presentation` or Docker Compose, and give the client `http://localhost:8080/mcp`. Both listen there.
+The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports), the current remote transport in the MCP spec, so any client that supports remote servers can connect to it. Start the server with Docker Compose and give the client `http://localhost:8080/mcp`. With `dotnet run` the address is `https://localhost:8081/mcp`, which needs a client that trusts the .NET development certificate, so Compose is the easier one to connect to.
No client to hand? Docker Compose can start one for you: see [Chat with it](../run-locally/#chat-with-it).
diff --git a/src/content/docs/mcp/guides/run-locally.mdx b/src/content/docs/mcp/guides/run-locally.mdx
index 9eab124..5970301 100644
--- a/src/content/docs/mcp/guides/run-locally.mdx
+++ b/src/content/docs/mcp/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`, the same port as Docker Compose. The server is at `/mcp`.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8081](https://localhost:8081), and on `https://localhost:8081`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once. The server is at `/mcp`.
```bash
@@ -58,7 +58,7 @@ The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is the of
- **Resources** lets you read `movies://top-rated`, `server://version` or any `movies://`, `authors://` or `reviews://` URI, and the ids autocomplete as you type an id or a title.
- **Prompts** renders each prompt with its arguments, and the title, name and id arguments autocomplete from the database.
-Running with `dotnet run` instead? Start the Inspector yourself with `npx @modelcontextprotocol/inspector` and point it at `http://localhost:8080/mcp`.
+Running with `dotnet run` instead? Start the Inspector yourself with `npx @modelcontextprotocol/inspector` and point it at `https://localhost:8081/mcp`. Node doesn't read the system's trusted certificates by default, so if it can't connect, use Docker Compose instead.
## Chat with it
@@ -83,21 +83,21 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 dotnet run --project ./src/Pre
## Try the tools from the terminal
-The server is stateless, so a single `tools/call` works without an `initialize` first. The sample data uses a fixed seed, so these ids are the same on every machine. See the [tool reference](../../reference/tools/) for every argument and result.
+The server is stateless, so a single `tools/call` works without an `initialize` first. These use the `dotnet run` address; with Docker Compose, use `http://localhost:8080/mcp` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [tool reference](../../reference/tools/) for every argument and result.
```bash
# One movie, with every review
-curl -X POST "http://localhost:8080/mcp" \
+curl -X POST "https://localhost:8081/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_get","arguments":{"movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28"}}}'
# Write a review
-curl -X POST "http://localhost:8080/mcp" \
+curl -X POST "https://localhost:8081/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"reviews_write","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}}}'
# Read a resource
-curl -X POST "http://localhost:8080/mcp" \
+curl -X POST "https://localhost:8081/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"server://version"}}'
```
diff --git a/src/content/docs/mcp/guides/secure-the-server.mdx b/src/content/docs/mcp/guides/secure-the-server.mdx
index dd49da8..bddd0af 100644
--- a/src/content/docs/mcp/guides/secure-the-server.mdx
+++ b/src/content/docs/mcp/guides/secure-the-server.mdx
@@ -42,7 +42,7 @@ Out of the box the server runs wide open, and there's no authentication code in
3. Get a token from the authority for that audience, and send it.
```bash
- curl -X POST "http://localhost:8080/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
+ curl -X POST "https://localhost:8081/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
diff --git a/src/content/docs/minimalapi/getting-started.mdx b/src/content/docs/minimalapi/getting-started.mdx
index 8de91fe..efe2ede 100644
--- a/src/content/docs/minimalapi/getting-started.mdx
+++ b/src/content/docs/minimalapi/getting-started.mdx
@@ -55,12 +55,14 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
-5. Open the Scalar API reference at [http://localhost:8080/scalar](http://localhost:8080/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
+ It answers on `https://localhost:8081`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+
+5. Open the Scalar API reference at [https://localhost:8081/scalar](https://localhost:8081/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
6. Or call it from the terminal.
```bash
- curl "http://localhost:8080/api/movies?query=space&limit=2"
+ curl "https://localhost:8081/api/movies?query=space&limit=2"
```
You get a page of movies back:
diff --git a/src/content/docs/minimalapi/guides/add-an-endpoint.mdx b/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
index 3dc998e..de14fe9 100644
--- a/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
+++ b/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
@@ -101,7 +101,7 @@ This guide adds `GET /api/movies/{id}/reviews`, one page of the reviews written
```bash
dotnet test
dotnet run --project ./src/Presentation
- curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
+ curl "https://localhost:8081/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
```
```json
diff --git a/src/content/docs/minimalapi/guides/run-locally.mdx b/src/content/docs/minimalapi/guides/run-locally.mdx
index 2707e7e..2248a06 100644
--- a/src/content/docs/minimalapi/guides/run-locally.mdx
+++ b/src/content/docs/minimalapi/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`, the same port as Docker Compose.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8081](https://localhost:8081), and on `https://localhost:8081`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
```bash
@@ -62,17 +62,17 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 dotnet run --project ./src/Pre
## Try the endpoints
-The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
+These use the `dotnet run` address; with Docker Compose, use `http://localhost:8080` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
```bash
# Search movies, two at a time
-curl "http://localhost:8080/api/movies?query=space&limit=2"
+curl "https://localhost:8081/api/movies?query=space&limit=2"
# One movie, with its reviews
-curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
+curl "https://localhost:8081/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
# Write a review
-curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:8081/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
diff --git a/src/content/docs/minimalapi/guides/secure-the-api.mdx b/src/content/docs/minimalapi/guides/secure-the-api.mdx
index ce5fac6..47aa2c7 100644
--- a/src/content/docs/minimalapi/guides/secure-the-api.mdx
+++ b/src/content/docs/minimalapi/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl "http://localhost:8080/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
+ curl "https://localhost:8081/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
```
Without a token, or with one from the wrong issuer or for the wrong audience, the API answers `401 Unauthorized`.
diff --git a/src/content/docs/minimalapi/reference/endpoints.mdx b/src/content/docs/minimalapi/reference/endpoints.mdx
index 325013c..0816b4e 100644
--- a/src/content/docs/minimalapi/reference/endpoints.mdx
+++ b/src/content/docs/minimalapi/reference/endpoints.mdx
@@ -7,7 +7,7 @@ sidebar:
Every endpoint lives in `src/Presentation/Endpoints`, one static class per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. In a solution created with `--include-auth`, every endpoint except health needs a token (see [Secure the API](../../guides/secure-the-api/)).
-The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it.
+The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it. The examples use the `dotnet run` address, `https://localhost:8081`; with Docker Compose it's `http://localhost:8080`.
| Method | Route | Returns |
| --- | --- | --- |
@@ -35,7 +35,7 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:8080/api/movies?query=space&limit=2"
+curl "https://localhost:8081/api/movies?query=space&limit=2"
```
```json
@@ -92,7 +92,7 @@ Lists reviews ordered by movie then author, one page at a time. Filter by movie,
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:8080/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
+curl "https://localhost:8081/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
```
```json
@@ -108,7 +108,7 @@ curl "http://localhost:8080/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab2
```
```bash
-curl "http://localhost:8080/api/reviews?query=funny&minStars=4&limit=2"
+curl "https://localhost:8081/api/reviews?query=funny&minStars=4&limit=2"
```
```json
@@ -139,7 +139,7 @@ Creates a review. The author and the movie must already exist.
| `text` | string | Optional. Up to 1000 characters |
```bash
-curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:8081/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
diff --git a/src/content/docs/webapi/getting-started.mdx b/src/content/docs/webapi/getting-started.mdx
index 52aacd0..fe8f2c0 100644
--- a/src/content/docs/webapi/getting-started.mdx
+++ b/src/content/docs/webapi/getting-started.mdx
@@ -55,12 +55,14 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
-5. Open the Scalar API reference at [http://localhost:8080/scalar](http://localhost:8080/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
+ It answers on `https://localhost:8081`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+
+5. Open the Scalar API reference at [https://localhost:8081/scalar](https://localhost:8081/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
6. Or call it from the terminal.
```bash
- curl "http://localhost:8080/api/movies?query=space&limit=2"
+ curl "https://localhost:8081/api/movies?query=space&limit=2"
```
You get a page of movies back:
diff --git a/src/content/docs/webapi/guides/add-an-endpoint.mdx b/src/content/docs/webapi/guides/add-an-endpoint.mdx
index 957173b..9c40c57 100644
--- a/src/content/docs/webapi/guides/add-an-endpoint.mdx
+++ b/src/content/docs/webapi/guides/add-an-endpoint.mdx
@@ -95,7 +95,7 @@ This guide adds `GET /api/movies/{id}/reviews`, one page of the reviews written
```bash
dotnet test
dotnet run --project ./src/Presentation
- curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
+ curl "https://localhost:8081/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
```
```json
diff --git a/src/content/docs/webapi/guides/run-locally.mdx b/src/content/docs/webapi/guides/run-locally.mdx
index abb95a4..2248a06 100644
--- a/src/content/docs/webapi/guides/run-locally.mdx
+++ b/src/content/docs/webapi/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8081](https://localhost:8081), and on `https://localhost:8081`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
```bash
@@ -62,17 +62,17 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 dotnet run --project ./src/Pre
## Try the endpoints
-The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
+These use the `dotnet run` address; with Docker Compose, use `http://localhost:8080` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
```bash
# Search movies, two at a time
-curl "http://localhost:8080/api/movies?query=space&limit=2"
+curl "https://localhost:8081/api/movies?query=space&limit=2"
# One movie, with its reviews
-curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
+curl "https://localhost:8081/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
# Write a review
-curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:8081/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
diff --git a/src/content/docs/webapi/guides/secure-the-api.mdx b/src/content/docs/webapi/guides/secure-the-api.mdx
index 9f4fa29..834308f 100644
--- a/src/content/docs/webapi/guides/secure-the-api.mdx
+++ b/src/content/docs/webapi/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl "http://localhost:8080/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
+ curl "https://localhost:8081/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
```
Without a token, or with one from the wrong issuer or for the wrong audience, the API answers `401 Unauthorized`.
diff --git a/src/content/docs/webapi/reference/endpoints.mdx b/src/content/docs/webapi/reference/endpoints.mdx
index 87d3b5f..4edc682 100644
--- a/src/content/docs/webapi/reference/endpoints.mdx
+++ b/src/content/docs/webapi/reference/endpoints.mdx
@@ -7,7 +7,7 @@ sidebar:
Every endpoint lives in `src/Presentation/Controllers`, one controller per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. In a solution created with `--include-auth`, every endpoint except health needs a token (see [Secure the API](../../guides/secure-the-api/)).
-The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it.
+The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it. The examples use the `dotnet run` address, `https://localhost:8081`; with Docker Compose it's `http://localhost:8080`.
| Method | Route | Returns |
| --- | --- | --- |
@@ -35,7 +35,7 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:8080/api/movies?query=space&limit=2"
+curl "https://localhost:8081/api/movies?query=space&limit=2"
```
```json
@@ -92,7 +92,7 @@ Lists reviews ordered by movie then author, one page at a time. Filter by movie,
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:8080/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
+curl "https://localhost:8081/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
```
```json
@@ -108,7 +108,7 @@ curl "http://localhost:8080/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab2
```
```bash
-curl "http://localhost:8080/api/reviews?query=funny&minStars=4&limit=2"
+curl "https://localhost:8081/api/reviews?query=funny&minStars=4&limit=2"
```
```json
@@ -139,7 +139,7 @@ Creates a review. The author and the movie must already exist.
| `text` | string | Optional. Up to 1000 characters |
```bash
-curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:8081/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
From 5a6d00cbe3c31c639335a4b7f4bb66f966e4fa30 Mon Sep 17 00:00:00 2001
From: Stephen Walsh <868017+stphnwlsh@users.noreply.github.com>
Date: Fri, 6 Nov 2026 23:03:49 +0000
Subject: [PATCH 3/3] Name both dotnet run ports in the Stuck tips
---
src/content/docs/graphql/guides/run-locally.mdx | 2 +-
src/content/docs/mcp/guides/run-locally.mdx | 2 +-
src/content/docs/minimalapi/guides/run-locally.mdx | 2 +-
src/content/docs/webapi/guides/run-locally.mdx | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/content/docs/graphql/guides/run-locally.mdx b/src/content/docs/graphql/guides/run-locally.mdx
index 6d44587..1c02489 100644
--- a/src/content/docs/graphql/guides/run-locally.mdx
+++ b/src/content/docs/graphql/guides/run-locally.mdx
@@ -117,5 +117,5 @@ docker build . --target run --tag acme-movies:latest \
The `version` query then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/mcp/guides/run-locally.mdx b/src/content/docs/mcp/guides/run-locally.mdx
index 5970301..f98e078 100644
--- a/src/content/docs/mcp/guides/run-locally.mdx
+++ b/src/content/docs/mcp/guides/run-locally.mdx
@@ -136,5 +136,5 @@ docker build . --target run --tag acme-movies:latest \
The `server://version` resource and the `serverInfo` in the initialize response then report `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/minimalapi/guides/run-locally.mdx b/src/content/docs/minimalapi/guides/run-locally.mdx
index 2248a06..c4751cf 100644
--- a/src/content/docs/minimalapi/guides/run-locally.mdx
+++ b/src/content/docs/minimalapi/guides/run-locally.mdx
@@ -110,5 +110,5 @@ docker build . --target run --tag acme-movies:latest \
`GET /api/version` then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/webapi/guides/run-locally.mdx b/src/content/docs/webapi/guides/run-locally.mdx
index 2248a06..c4751cf 100644
--- a/src/content/docs/webapi/guides/run-locally.mdx
+++ b/src/content/docs/webapi/guides/run-locally.mdx
@@ -110,5 +110,5 @@ docker build . --target run --tag acme-movies:latest \
`GET /api/version` then reports `1.2.3-beta` and the commit it was built from.