From b3faf0e98ee5424528c3f869ed27483f84713b40 Mon Sep 17 00:00:00 2001 From: Stephen Walsh <868017+stphnwlsh@users.noreply.github.com> Date: Fri, 6 Nov 2026 09:56:00 -0600 Subject: [PATCH 1/2] Add an optional chat demo with Open WebUI behind a compose profile --- .agents/skills/docker-and-compose/SKILL.md | 2 ++ .agents/skills/mcp-inspector/SKILL.md | 4 +++ README.md | 2 ++ docker-compose.yml | 30 ++++++++++++++++++++++ docs/RUN.md | 11 ++++++++ 5 files changed, 49 insertions(+) diff --git a/.agents/skills/docker-and-compose/SKILL.md b/.agents/skills/docker-and-compose/SKILL.md index 97c4488..3812740 100644 --- a/.agents/skills/docker-and-compose/SKILL.md +++ b/.agents/skills/docker-and-compose/SKILL.md @@ -23,6 +23,8 @@ One multi-stage `Dockerfile` does everything the pipeline needs: The Lambda template's `run` stage is the AWS Lambda .NET base image instead. It runs as user `65534` from `/var/task`, keeps its database in `/tmp`, and has no `HEALTHCHECK`. Its compose file runs the collector and the Aspire dashboard as well. Compose maps the runtime interface emulator to port 9000, so the function can be invoked with a `POST` to `/2015-03-31/functions/function/invocations`. +The `demo` profile adds Open WebUI for a chat over the tools (`mcp-inspector`). Keep it behind the profile so plain `docker compose up` stays the same, and inside its `#if (!includeAuth)` blocks. + ## Steps 1. Keep restore before `COPY . .` so a code change doesn't redo the restore layer. A new project folder needs nothing extra; the loops pick up every `src/*/*.csproj` and `tests/*/*.csproj`. diff --git a/.agents/skills/mcp-inspector/SKILL.md b/.agents/skills/mcp-inspector/SKILL.md index 70934c5..4a8554d 100644 --- a/.agents/skills/mcp-inspector/SKILL.md +++ b/.agents/skills/mcp-inspector/SKILL.md @@ -26,6 +26,10 @@ metadata: 5. Find the tool call in the Aspire dashboard if something looks wrong: its trace shows the tool, the use case and each SQL command, and its logs sit beside it (`telemetry`). 6. With authentication on, the Inspector needs a token from your authority. Leave `Authentication:Authority` empty for local checks. +## Chat demo + +To try the tools the way a model does, add the optional `demo` profile: `docker compose --profile demo up -d --build`. It starts [Open WebUI](https://github.com/open-webui/open-webui) at , already connected to the server and using Kilo's free gateway as the model, with no account or key. It's online only, and your prompts and tool results go to Kilo, a third party, so use it with sample data. Plain `docker compose up` doesn't start it, and solutions created with `--include-auth` leave it out, because Open WebUI has no token to send. + ## Don't - Expose port 6274 beyond `127.0.0.1`. The Inspector can call every tool. diff --git a/README.md b/README.md index 4c78ad2..8303306 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ Yes, the sample movies are named by a corporate buzzword generator, and the crit Curl is a fine way to prove it works and a terrible way to spend an afternoon. Open the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) at instead. It's already pointed at the server, so flip the switch to connect and open **Tools**. +For a chat over the same tools, `docker compose --profile demo up -d --build` adds [Open WebUI](https://github.com/open-webui/open-webui) at . It's optional and online only, and your prompts go to Kilo's free gateway, a third party. [Run it](docs/RUN.md#chat-demo) has the details. + There are six tools shaped around jobs rather than tables: `movies_search`, `movies_get`, `authors_search`, `reviews_list`, `reviews_write` and `reviews_delete`. Each has a description written for agents and hints that say what's read-only and what's destructive. Lists come back ten at a time, with a `totalCount` and a `nextOffset` when there's more to come, so an agent doesn't drown in its own context window. There are resources for every movie, author and review, and prompts for jobs like comparing two movies. Its logs, traces and metrics land in the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at . No Docker? `dotnet run --project src/Presentation` works too. Every other way to run it, from plain `docker run` to stamping a version into the image, is in [docs/RUN.md](docs/RUN.md). diff --git a/docker-compose.yml b/docker-compose.yml index 5de0958..519e3d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,8 +46,38 @@ services: ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS: "true" networks: - cleanmodelcontextprotocol_network + #if (!includeAuth) + # Optional chat over the MCP server: docker compose --profile demo up -d --build. Prompts go to Kilo, a third party. + chat: + image: ghcr.io/open-webui/open-webui:v0.13.0 + profiles: + - demo + ports: + - "127.0.0.1:3000:8080" + environment: + # Kilo's free gateway takes no API key, and a dummy one gets a 401. + OPENAI_API_BASE_URL: https://api.kilo.ai/api/gateway + DEFAULT_MODELS: kilo-auto/free + ENABLE_OLLAMA_API: "False" + WEBUI_AUTH: "False" + TOOL_SERVER_CONNECTIONS: '[{"url":"http://api:8080/mcp","path":"","type":"mcp","auth_type":"none","key":"","config":{"enable":true},"info":{"id":"mcp","name":"MCP server"}}]' + # Turns the MCP server on in every chat, and leaves out Open WebUI's own tools so the model only sees ours. + DEFAULT_MODEL_METADATA: '{"toolIds":["server:mcp:mcp"],"capabilities":{"builtin_tools":false}}' + # These two skip the embedding model download from Hugging Face on first start. + OFFLINE_MODE: "True" + BYPASS_EMBEDDING_AND_RETRIEVAL: "True" + volumes: + - chat-data:/app/backend/data + networks: + - cleanmodelcontextprotocol_network + depends_on: + - api + #endif volumes: movie-reviews-data: + #if (!includeAuth) + chat-data: + #endif networks: cleanmodelcontextprotocol_network: driver: bridge diff --git a/docs/RUN.md b/docs/RUN.md index 136d459..f55d1c1 100644 --- a/docs/RUN.md +++ b/docs/RUN.md @@ -42,6 +42,17 @@ To shut the containers down: docker compose down ``` + +### Chat demo + +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 , 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`. + ## Telemetry The app always collects [OpenTelemetry](https://opentelemetry.io) traces and metrics, and exports them, along with its logs, only when `OTEL_EXPORTER_OTLP_ENDPOINT` is set. Every other standard `OTEL_*` variable works too, such as `OTEL_SERVICE_NAME`, `OTEL_EXPORTER_OTLP_PROTOCOL` and `OTEL_EXPORTER_OTLP_HEADERS`, so sending it to Grafana, Honeycomb, Seq or a collector is a settings change. From 9317025e02d504e05e0fa24b6fa9a1bc3f2ad1b4 Mon Sep 17 00:00:00 2001 From: Stephen Walsh <868017+stphnwlsh@users.noreply.github.com> Date: Fri, 6 Nov 2026 10:01:51 -0600 Subject: [PATCH 2/2] Keep the template symbol out of the shipped compose skill --- .agents/skills/docker-and-compose/SKILL.md | 2 +- .agents/skills/template-authoring/SKILL.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.agents/skills/docker-and-compose/SKILL.md b/.agents/skills/docker-and-compose/SKILL.md index 3812740..93a892f 100644 --- a/.agents/skills/docker-and-compose/SKILL.md +++ b/.agents/skills/docker-and-compose/SKILL.md @@ -23,7 +23,7 @@ One multi-stage `Dockerfile` does everything the pipeline needs: The Lambda template's `run` stage is the AWS Lambda .NET base image instead. It runs as user `65534` from `/var/task`, keeps its database in `/tmp`, and has no `HEALTHCHECK`. Its compose file runs the collector and the Aspire dashboard as well. Compose maps the runtime interface emulator to port 9000, so the function can be invoked with a `POST` to `/2015-03-31/functions/function/invocations`. -The `demo` profile adds Open WebUI for a chat over the tools (`mcp-inspector`). Keep it behind the profile so plain `docker compose up` stays the same, and inside its `#if (!includeAuth)` blocks. +The `demo` profile adds Open WebUI for a chat over the tools (`mcp-inspector`). Keep it behind the profile so plain `docker compose up` stays the same. Solutions generated with authentication on leave it out. ## Steps diff --git a/.agents/skills/template-authoring/SKILL.md b/.agents/skills/template-authoring/SKILL.md index 4236e7b..131eb65 100644 --- a/.agents/skills/template-authoring/SKILL.md +++ b/.agents/skills/template-authoring/SKILL.md @@ -39,6 +39,8 @@ Every optional feature is a toggle, and every toggle has the same shape, so peop Off means absent, not switched off. Files that only exist for a toggle go in a `(!include)` exclude list under `sources` in `template.json`, and code inside shared files is wrapped so `dotnet new` can leave it out. In C#, `#if includeAuth` and `#endif` start at the beginning of the line, with no brackets (the build fails IDE0047 and IDE0055 otherwise). In project files it's `` and ``, and in `appsettings.json` it's `//#if (includeAuth)` and `//#endif`. A file type the template engine doesn't know, such as the GraphQL schema snapshot, gets `#if (includeAuth)` and `#endif` lines through `SpecialCustomOperations` in `template.json`. +The `chat` service and its volume in `docker-compose.yml` sit in `#if (!includeAuth)` blocks, because Open WebUI has no token to send. A shipped skill must describe that without naming the symbol: Verify Template fails if the word survives in a generated solution. + The repository itself stays all on. The Presentation project and the presentation integration tests define each toggle's constant, such as `includeAuth`, inside ``, which `dotnet new` always removes. The template's skills and `AGENTS.md` ship either way. A toggle's inputs, such as `authority` and `audience`, are separate symbols, enabled with `isEnabled` and made required with `isRequired` only when the toggle is on. Only `dotnet new` honours those two. Visual Studio and Rider show every input whatever the toggle says ([dotnet/templating#7860](https://github.com/dotnet/templating/issues/7860)), and Rider lists checkboxes first, then text fields in alphabetical order by label. So an input's label starts with its feature, such as "Authentication authority", to sit right under its checkbox, and its IDE description says it's only used when that box is ticked. A toggle that needs another one says so in its conditions, such as `(includeTests && includeArchitectureTests)`. Verify Template checks the defaults, everything on, everything off, and each toggle flipped on its own.