diff --git a/README.md b/README.md index c1ec166..739ca6a 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ Each sample has a companion cookbook page with explanations and patterns. | **[Hosting Remote](samples/05-hosting-remote/)** — remote interactive REPL sessions | [repl.yllibed.org/cookbook/hosting-remote/](https://repl.yllibed.org/cookbook/hosting-remote/) | | **[Testing](samples/06-testing/)** — multi-session typed assertions | [repl.yllibed.org/cookbook/testing/](https://repl.yllibed.org/cookbook/testing/) | | **[Spectre](samples/07-spectre/)** — Spectre.Console renderables, visualizations, rich prompts | [repl.yllibed.org/cookbook/spectre/](https://repl.yllibed.org/cookbook/spectre/) | -| **[MCP Server](samples/08-mcp-server/)** — MCP tools, resources, prompts, and MCP Apps UI | [repl.yllibed.org/cookbook/mcp-server/](https://repl.yllibed.org/cookbook/mcp-server/) | +| **[Build an MCP Server with Repl.Mcp](samples/08-mcp-server/)** — MCP tools, resources, prompts, and MCP Apps UI | [repl.yllibed.org/cookbook/mcp-server/](https://repl.yllibed.org/cookbook/mcp-server/) | ## More documentation diff --git a/docs/mcp-overview.md b/docs/mcp-overview.md index 931734e..3cdee8f 100644 --- a/docs/mcp-overview.md +++ b/docs/mcp-overview.md @@ -31,6 +31,7 @@ myapp # still works as CLI / interactive REPL One command graph. CLI, REPL, remote sessions, and AI agents — all from the same code. > `UseMcpServer()` registers a hidden `mcp serve` context. The tool list is built lazily when an agent connects, so it sees all commands regardless of registration order. +> Repl.Mcp is the component that lets your app become an MCP server; it is not itself the MCP server you configure in an agent host. ## What it does @@ -105,7 +106,7 @@ Most MCP clients use the same format: } ``` -See [mcp-reference.md](mcp-reference.md#agent-configuration) for all client-specific paths and formats (Claude Desktop, Claude Code, VS Code Copilot, Cursor, MCP Inspector). +See [mcp-reference.md](mcp-reference.md#agent-configuration) for all client-specific paths and formats (Claude Desktop, Claude Code, VS Code Copilot, Cursor, Cline, MCP Inspector). For a complete copy/paste sample, see [sample 08 — Build an MCP Server with Repl.Mcp](../samples/08-mcp-server/). ## What most apps need vs what few apps need diff --git a/docs/mcp-reference.md b/docs/mcp-reference.md index 4ea3eb3..5190316 100644 --- a/docs/mcp-reference.md +++ b/docs/mcp-reference.md @@ -335,9 +335,20 @@ app.UseMcpServer(o => ## Agent configuration -### Claude Desktop +Agent hosts configure the app or tool you built with Repl.Mcp. They do not +install Repl.Mcp directly. -**File:** `~/.config/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows) +Prefer a stable executable command, such as a published `dotnet tool`, for +shared team configs. When documenting a local sample, build it once and use +`dotnet run --no-build`, or point the host at a published executable. Plain +`dotnet run --project ...` is fragile in host configs because cold builds can +exceed startup timeouts and build/restore output can reach stdout before the MCP +JSON-RPC stream starts. + +### Generic MCP client / Claude Desktop + +**File:** `~/Library/Application Support/Claude/claude_desktop_config.json` +(macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows) ```json { @@ -350,24 +361,51 @@ app.UseMcpServer(o => } ``` -### Claude Code - -**File:** `~/.claude.json` or project `.claude/settings.json` +For a local sample project, split the launch into `command` and `args`: ```json { "mcpServers": { - "myapp": { - "command": "myapp", - "args": ["mcp", "serve"] + "repl-contacts-sample": { + "command": "dotnet", + "args": [ + "run", + "--no-build", + "--project", + "/absolute/path/to/repl/samples/08-mcp-server/McpServerSample.csproj", + "--", + "mcp", + "serve" + ] } } } ``` -### VS Code (GitHub Copilot) +On Windows JSON, write paths with doubled backslashes, for example +`C:\\Users\\you\\src\\repl\\samples\\08-mcp-server\\McpServerSample.csproj`. + +### Claude Code -**File:** `.vscode/mcp.json` (workspace) or `~/.mcp.json` (global) +Claude Code uses command registration as the standard flow: + +```bash +claude mcp add myapp -- myapp mcp serve +``` + +For a local Repl sample after an initial build: + +```bash +claude mcp add repl-contacts-sample -- dotnet run --no-build --project /absolute/path/to/repl/samples/08-mcp-server/McpServerSample.csproj -- mcp serve +``` + +For file-based provisioning, use `.mcp.json` at the project root for project +settings or `~/.claude.json` for user/local settings. Both use the generic +`mcpServers` JSON shape. + +### VS Code / GitHub Copilot + +**File:** `.vscode/mcp.json` (workspace) ```json { @@ -381,20 +419,34 @@ app.UseMcpServer(o => } ``` +VS Code also supports command-line registration from macOS/Linux shells: + +```bash +code --add-mcp '{"name":"myapp","command":"myapp","args":["mcp","serve"]}' +``` + +On Windows, prefer the `.vscode/mcp.json` file. The `code.cmd --add-mcp` +argument path can mangle inline JSON in common shells; if you write JSON by +hand, remember to escape backslashes in Windows paths. + ### Cursor **File:** `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global) -```json -{ - "mcpServers": { - "myapp": { - "command": "myapp", - "args": ["mcp", "serve"] - } - } -} -``` +Cursor uses the generic `mcpServers` JSON shape. + +### Cline + +Use **Configure MCP Servers** to edit `cline_mcp_settings.json`, then add the +local stdio server with the generic `mcpServers` JSON shape. The Cline +marketplace is for published/curated servers and will not install an unpublished +local sample. + +### Complete copy/paste sample + +See [sample 08 — Build an MCP Server with Repl.Mcp](../samples/08-mcp-server/) +for Cursor, VS Code, Claude Code, Cline, generic JSON, and MCP Inspector +examples. ### Debugging with MCP Inspector @@ -404,7 +456,16 @@ Use the UI for interactive exploration: npx @modelcontextprotocol/inspector myapp mcp serve ``` -Use CLI mode for repeatable smoke checks. `resources/list` exposes the advertised resource MIME type, and `resources/read` exposes the MIME type and body returned on the wire: +For a local sample project, build first and pass the same no-build command used +by agent hosts: + +```bash +npx @modelcontextprotocol/inspector dotnet run --no-build --project /absolute/path/to/repl/samples/08-mcp-server/McpServerSample.csproj -- mcp serve +``` + +Use CLI mode for repeatable smoke checks. `resources/list` exposes the +advertised resource MIME type, and `resources/read` exposes the MIME type and +body returned on the wire: ```bash # Build or publish the server first; this example uses a built sample DLL. @@ -422,7 +483,8 @@ npx -y @modelcontextprotocol/inspector@0.22.0 --cli \ | jq '.contents[] | { uri, mimeType, text }' ``` -The repository also includes an opt-in `dotnet test` smoke guard for this external toolchain: +The repository also includes an opt-in `dotnet test` smoke guard for this +external toolchain: ```bash REPL_RUN_MCP_INSPECTOR_TESTS=1 \ @@ -430,11 +492,19 @@ REPL_RUN_MCP_INSPECTOR_TESTS=1 \ --filter 'TestCategory=ExternalToolchain' ``` -It is skipped by default so the normal .NET test suite stays hermetic and does not require Node/npm or npm registry access. +It is skipped by default so the normal .NET test suite stays hermetic and does +not require Node/npm or npm registry access. -For command-level tests, use `Repl.Testing`: `CommandExecution.GetResult()` validates the handler return value before rendering, while `OutputText` / `ReadJson()` validate rendered output. For MCP wire contracts such as `Resource.MimeType` and `TextResourceContents.MimeType`, use the MCP test fixture or the opt-in Inspector CLI smoke check because those values are protocol metadata, not `Repl.Testing` command results. +For command-level tests, use `Repl.Testing`: `CommandExecution.GetResult()` +validates the handler return value before rendering, while `OutputText` / +`ReadJson()` validate rendered output. For MCP wire contracts such as +`Resource.MimeType` and `TextResourceContents.MimeType`, use the MCP test +fixture or the opt-in Inspector CLI smoke check because those values are +protocol metadata, not `Repl.Testing` command results. -Command-backed resources expose the rendered handler return value as the resource body. Low-level writes to `IReplIoContext.Output` are treated as side-channel command output and are not included in `resources/read` bodies. +Command-backed resources expose the rendered handler return value as the +resource body. Low-level writes to `IReplIoContext.Output` are treated as +side-channel command output and are not included in `resources/read` bodies. ## Client compatibility diff --git a/docs/result-flow.md b/docs/result-flow.md index d9a81c9..7a3c4ab 100644 --- a/docs/result-flow.md +++ b/docs/result-flow.md @@ -765,7 +765,7 @@ dependency-free and can opt in by registering their own - [Core Basics sample](../samples/01-core-basics/README.md#result-flow-paging) - [Spectre sample](../samples/07-spectre/README.md#activity--paged-long-data-source) -- [MCP Server sample](../samples/08-mcp-server/README.md#demo-workflow) +- [MCP Server sample](../samples/08-mcp-server/README.md#test-with-mcp-inspector) - [Output System](output-system.md) - [Command Reference](commands.md) - [MCP Reference](mcp-reference.md) diff --git a/samples/08-mcp-server/Program.cs b/samples/08-mcp-server/Program.cs index 77b84e7..0ecab15 100644 --- a/samples/08-mcp-server/Program.cs +++ b/samples/08-mcp-server/Program.cs @@ -7,11 +7,14 @@ // ── A Repl app exposed as an MCP server for AI agents ────────────── // -// Run interactively: dotnet run -// Run as MCP server: dotnet run -- mcp serve +// Repl.Mcp is the component that lets this sample app become an MCP server. +// Agent hosts install/configure the sample app command, not Repl.Mcp itself. // -// Configure in Claude Desktop or VS Code: -// { "command": "dotnet", "args": ["run", "--project", "path/to/08-mcp-server", "--", "mcp", "serve"] } +// Run interactively from this project: dotnet run +// Run as MCP server after building: dotnet run --no-build -- mcp serve +// +// Configure an MCP host with an absolute project path and no build step: +// { "command": "dotnet", "args": ["run", "--no-build", "--project", "/absolute/path/to/repl/samples/08-mcp-server/McpServerSample.csproj", "--", "mcp", "serve"] } var app = ReplApp.Create(services => { diff --git a/samples/08-mcp-server/README.md b/samples/08-mcp-server/README.md index f2cefd2..45bfa42 100644 --- a/samples/08-mcp-server/README.md +++ b/samples/08-mcp-server/README.md @@ -1,10 +1,15 @@ -# 08 — MCP Server +# 08 — Build an MCP Server with Repl.Mcp -Expose a Repl command graph as an MCP server for AI agents, including a minimal MCP Apps UI. +This sample shows how to build a real MCP server from a Repl command graph. + +> **Important:** Repl.Mcp is not itself the MCP server you install in an agent host. +> Repl.Mcp is the Repl Toolkit component that lets your app expose its own command graph as an MCP server. +> In this sample, the sample app is the MCP server. ## What this sample shows -- `app.UseMcpServer()` — one line to enable MCP stdio server +- `app.UseMcpServer()` — one line to enable an MCP stdio server for this app +- One command graph exposed as CLI, interactive REPL, and MCP tools/resources/prompts - `contacts paged` — paged structured output for large result sets - `IReplInteractionChannel` in MCP mode — portable notices, warnings, problems, and progress updates - `feedback demo` / `feedback fail` — deterministic progress sequences that are easy to inspect in MCP Inspector @@ -14,42 +19,78 @@ Expose a Repl command graph as an MCP server for AI agents, including a minimal - `.AsMcpAppResource()` — mark a command as a generated HTML MCP App resource - `.WithMcpAppBorder()` / `.WithMcpAppDisplayMode(...)` — add MCP Apps presentation preferences - `.AutomationHidden()` — hide interactive-only commands from agents -- `.WithDetails()` — rich descriptions consumed by agents and documentation tools (not displayed in `--help`) +- `.WithDetails()` — rich descriptions consumed by agents and documentation tools, not displayed in `--help` - `import {file}` — a realistic workflow that combines progress reporting, sampling, elicitation, and duplicate review -## Running +## Run from the repository root + +Use these commands from the root of the `yllibed/repl` repository. -**Interactive REPL:** +### CLI mode ```bash -dotnet run +dotnet run --project samples/08-mcp-server/McpServerSample.csproj -- contacts --json +``` + +Expected shape: + +```json +[ + { + "name": "Alice", + "email": "alice@example.com" + }, + { + "name": "Bob", + "email": "bob@example.com" + } +] ``` -**MCP server mode:** +Try another CLI command: ```bash -dotnet run -- mcp serve +dotnet run --project samples/08-mcp-server/McpServerSample.csproj -- contact 1 --json ``` -**Test with MCP Inspector:** +### Interactive REPL mode ```bash -npx @modelcontextprotocol/inspector dotnet run --project . -- mcp serve +dotnet run --project samples/08-mcp-server/McpServerSample.csproj ``` -Clients with MCP Apps support render the `contacts dashboard` tool's generated `ui://contacts/dashboard` resource. Other clients still receive the normal launcher text instead of raw HTML. +Then try: -In the current Repl.Mcp version, MCP Apps are experimental and the UI handler returns generated HTML as a string. Future versions may add richer return types and asset helpers. +```text +contacts +contacts paged --result:page-size=5 +contacts paged --result:page-size=5 --result:cursor=5 +contact 1 +feedback demo +exit +``` + +### MCP server mode + +Build the sample once before launching it from MCP Inspector or an agent host: + +```bash +dotnet build samples/08-mcp-server/McpServerSample.csproj +``` -## Demo workflow +Then start the stdio MCP server without rebuilding: -In the interactive REPL, try: +```bash +dotnet run --no-build --project samples/08-mcp-server/McpServerSample.csproj -- mcp serve +``` + +This starts a stdio MCP server for the sample app. It waits for an MCP client to connect over stdin/stdout. + +### Test with MCP Inspector -- `contacts paged --result:page-size=5` to inspect the first page of a synthetic long directory -- `contacts paged --result:page-size=5 --result:cursor=5` to continue from the next cursor -- `feedback demo` to emit a successful sequence with normal, indeterminate, and warning progress states -- `feedback fail` to emit warning and error progress, then finish with a problem result -- `import contacts.csv` to see the realistic workflow that uses sampling and elicitation when the connected client supports them +```bash +npx @modelcontextprotocol/inspector dotnet run --no-build --project samples/08-mcp-server/McpServerSample.csproj -- mcp serve +``` In MCP Inspector: @@ -65,31 +106,121 @@ In MCP Inspector: The deterministic `feedback_*` tools make it easy to verify the host's notification rendering without depending on a real CSV file. -## Agent configuration +Clients with MCP Apps support render the `contacts dashboard` tool's generated `ui://contacts/dashboard` resource. Other clients still receive the normal launcher text instead of raw HTML. + +In the current Repl.Mcp version, MCP Apps are experimental and the UI handler returns generated HTML as a string. Future versions may add richer return types and asset helpers. + +## Agent host configuration + +Agent hosts should launch a stable, prebuilt command. Plain +`dotnet run --project ...` is fragile in MCP host configs: a cold build can +exceed host startup timeouts, and build or restore output can reach stdout +before the MCP JSON-RPC stream starts. + +For this sample: + +1. Build once from the repository root: + + ```bash + dotnet build samples/08-mcp-server/McpServerSample.csproj + ``` + +2. Put `dotnet run --no-build --project ... -- mcp serve` in host config, or + replace it with a published app executable if you package the sample. -### Claude Desktop +Replace path placeholders before copying: + +- macOS/Linux JSON: `/absolute/path/to/repl`. +- Windows JSON: `C:\\Users\\you\\src\\repl` (JSON requires doubled + backslashes). + +### Shared `mcpServers` JSON + +Use this shape for Claude Desktop, Cursor, Cline, and generic MCP clients: ```json { "mcpServers": { - "contacts": { + "repl-contacts-sample": { "command": "dotnet", - "args": ["run", "--project", "samples/08-mcp-server", "--", "mcp", "serve"] + "args": [ + "run", + "--no-build", + "--project", + "/absolute/path/to/repl/samples/08-mcp-server/McpServerSample.csproj", + "--", + "mcp", + "serve" + ] } } } ``` -### VS Code (GitHub Copilot) +A copyable file lives at +[`configs/generic-mcp-client.json`](configs/generic-mcp-client.json). -```json -{ - "servers": { - "contacts": { - "type": "stdio", - "command": "dotnet", - "args": ["run", "--project", "samples/08-mcp-server", "--", "mcp", "serve"] - } - } -} +Common file locations: + +- Claude Desktop macOS: + `~/Library/Application Support/Claude/claude_desktop_config.json` +- Claude Desktop Windows: + `%APPDATA%\Claude\claude_desktop_config.json` +- Cursor project config: `.cursor/mcp.json` +- Cursor global config: `~/.cursor/mcp.json` +- Cline: use **Configure MCP Servers** to edit `cline_mcp_settings.json`. + The Cline marketplace is for published/curated servers; it will not install + this local sample. + +### VS Code / GitHub Copilot + +Workspace config path: `.vscode/mcp.json`. + +A copyable file lives at [`configs/vscode.mcp.json`](configs/vscode.mcp.json). + +For macOS/Linux shells, VS Code also supports command-line registration: + +```bash +code --add-mcp '{"name":"repl-contacts-sample","command":"dotnet","args":["run","--no-build","--project","/absolute/path/to/repl/samples/08-mcp-server/McpServerSample.csproj","--","mcp","serve"]}' +``` + +On Windows, prefer editing `.vscode/mcp.json`. The `code.cmd --add-mcp` +argument path can mangle inline JSON in common shells. If you write JSON by +hand, use doubled backslashes in paths, for example +`C:\\Users\\you\\src\\repl\\samples\\08-mcp-server\\McpServerSample.csproj`. + +### Claude Code + +Use the standard command registration flow: + +```bash +claude mcp add repl-contacts-sample -- dotnet run --no-build --project /absolute/path/to/repl/samples/08-mcp-server/McpServerSample.csproj -- mcp serve ``` + +For file-based provisioning, use the shared `mcpServers` JSON shape above in +`.mcp.json` at the project root or in `~/.claude.json` for user/local settings. + +## Adapting this sample to your app + +1. Keep your command handlers small, typed, and dependency-injected. +2. Return JSON-friendly objects instead of prose-only console output. +3. Add `app.UseMcpServer()` to register the hidden `mcp serve` command. +4. Annotate commands that agents can call: + - `.ReadOnly()` for safe queries; + - `.Destructive()` for mutations that need confirmation; + - `.Idempotent()` for operations safe to retry; + - `.OpenWorld()` for external systems; + - `.LongRunning()` for slow work; + - `.AutomationHidden()` for interactive-only or unsafe commands. +5. Document the exact MCP command and args your users should paste into their agent host. + +## Safety note + +Local MCP servers run commands on the user's machine. Only configure MCP servers from trusted repositories, review the command and arguments before starting them, and avoid hardcoding secrets in MCP config files. Prefer environment variables or host-supported secret inputs for API keys. + +## References + +- [MCP Server Integration](../../docs/mcp-overview.md) +- [MCP Server Reference](../../docs/mcp-reference.md) +- [MCP agent capabilities](../../docs/mcp-agent-capabilities.md) +- [MCP transports](../../docs/mcp-transports.md) diff --git a/samples/08-mcp-server/configs/generic-mcp-client.json b/samples/08-mcp-server/configs/generic-mcp-client.json new file mode 100644 index 0000000..2e94cf4 --- /dev/null +++ b/samples/08-mcp-server/configs/generic-mcp-client.json @@ -0,0 +1,16 @@ +{ + "mcpServers": { + "repl-contacts-sample": { + "command": "dotnet", + "args": [ + "run", + "--no-build", + "--project", + "/absolute/path/to/repl/samples/08-mcp-server/McpServerSample.csproj", + "--", + "mcp", + "serve" + ] + } + } +} diff --git a/samples/08-mcp-server/configs/vscode.mcp.json b/samples/08-mcp-server/configs/vscode.mcp.json new file mode 100644 index 0000000..057eef0 --- /dev/null +++ b/samples/08-mcp-server/configs/vscode.mcp.json @@ -0,0 +1,17 @@ +{ + "servers": { + "repl-contacts-sample": { + "type": "stdio", + "command": "dotnet", + "args": [ + "run", + "--no-build", + "--project", + "/absolute/path/to/repl/samples/08-mcp-server/McpServerSample.csproj", + "--", + "mcp", + "serve" + ] + } + } +} diff --git a/samples/README.md b/samples/README.md index 0363665..159c2c6 100644 --- a/samples/README.md +++ b/samples/README.md @@ -20,8 +20,8 @@ If you’re new, start with **01**, then follow the sequence. `Repl.Testing` harness: multi-step + multi-session, typed results, interaction/timeline events, metadata snapshots. 7. [07 — Spectre](07-spectre/) `Repl.Spectre` integration: FigletText, Table, paged result tables, Panel, Tree, BarChart, BreakdownChart, Calendar, JsonText, TextPath, Grid, Columns, Rule, Status, Progress, and all Spectre-powered prompts. -8. [08 — MCP Server](08-mcp-server/) - MCP server mode: tools, paged structured results, resources, prompts, behavioral annotations, automation visibility, and a minimal MCP Apps UI. +8. [08 — Build an MCP Server with Repl.Mcp](08-mcp-server/) + Build a concrete MCP server from a Repl command graph: CLI + REPL + MCP mode, host config snippets for Cursor/VS Code/Claude/Cline, tools, resources, prompts, behavioral annotations, automation visibility, and a minimal MCP Apps UI. ## Run diff --git a/src/Repl.Mcp/README.md b/src/Repl.Mcp/README.md index f3e4e52..06ee966 100644 --- a/src/Repl.Mcp/README.md +++ b/src/Repl.Mcp/README.md @@ -49,7 +49,7 @@ Most MCP clients use the same shape: } ``` -Use the executable or `dotnet run --project ... -- mcp serve` command that matches your app packaging. +Use the executable that matches your app packaging. For local project samples, build once and use `dotnet run --no-build --project ... -- mcp serve` so host startup does not rebuild or write build output to stdout. ## MCP Apps