MCP Server
The Model Context Protocol (MCP) is the open standard through which AI agents discover and call tools. Repl.Mcp makes your entire command surface MCP-native with one line of code — then lets you fine-tune every aspect of how it’s exposed.
Enable MCP
Section titled “Enable MCP”using Repl.Mcp;
app.UseMcpServer(); // exposes everything as MCP toolsreturn app.Run(args);Start the server:
myapp mcp serveConfigure your agent (paths vary by client — see below):
{ "mcpServers": { "myapp": { "command": "myapp", "args": ["mcp", "serve"] } }}Agent config file locations:
| Agent | Path |
|---|---|
| Claude Desktop | %APPDATA%\Claude\claude_desktop_config.json (Windows) / ~/.config/Claude/... (macOS) |
| Claude Code | .claude/settings.json or ~/.claude.json |
| VS Code Copilot | .vscode/mcp.json or ~/.mcp.json |
| Cursor | .cursor/mcp.json or ~/.cursor/mcp.json |
| MCP Inspector | UI-based |
Command → MCP mapping
Section titled “Command → MCP mapping”Every Map(...) call becomes an MCP tool automatically. The tool name is derived from the route (spaces replaced by _). The description comes from [Description] or .WithDescription(...).
| Repl declaration | MCP surface | Notes |
|---|---|---|
app.Map(...) | Tool | Default — always a tool |
.ReadOnly() | Tool + auto-promoted Resource | URI derived from route |
.AsResource() | Resource only | Not callable as tool; URI derived from route |
.AsPrompt() | Prompt | Callable as MCP prompt |
.AsMcpAppResource() | Tool + ui:// HTML resource | Returns HTML for capable clients |
.AutomationHidden() | Excluded | Human-only command |
Tool naming
Section titled “Tool naming”Default: contact add → contact_add. Customize the separator:
app.UseMcpServer(o => o.ToolNamingSeparator = ToolNamingSeparator.Hyphen);// contact add → contact-addBehavioral annotations
Section titled “Behavioral annotations”Annotations tell the agent how to call your tool — whether it should ask for confirmation, whether it’s safe to parallelize, whether it can be retried:
app.Map("contacts delete {id:int}", (int id, IContactStore store) =>{ store.Delete(id); return Results.Success("Contact deleted.");}).WithDescription("Permanently delete a contact.").Destructive(); // warn agent before calling; not safe to parallelize| Annotation | Meaning for the agent |
|---|---|
.ReadOnly() | No side effects — call freely, parallelize |
.Destructive() | Side effects — warn user, don’t parallelize |
.Idempotent() | Safe to retry on transient failure |
.OpenWorld() | Reaches external systems (email, HTTP, etc.) |
.LongRunning() | Enable call-now/poll-later pattern |
.AutomationHidden() | Exclude from MCP; visible in interactive REPL only |
Resources
Section titled “Resources”Expose read-only data as MCP resources — the agent can subscribe, list, and read without invoking a tool:
app.Map("contacts export", (IContactStore store) => store.All()) .AsResource() .WithDescription("All contacts as a live JSON resource.");The resource URI is derived from the route.
Use .ReadOnly() for the common case of “tool that’s also readable as a resource”:
app.Map("contacts {id:int} show", (int id, IContactStore store) => store.Get(id)) .ReadOnly() .WithDescription("Get contact by ID.");// Registered as both tool 'contacts_show' and a resource with a URI derived from the routePrompts
Section titled “Prompts”Prompt templates let the agent inject pre-built context into an LLM conversation:
app.Map("prompts summarize {id:int}", (int id, IContactStore store) =>{ var contact = store.Get(id); return new McpPromptResult { Messages = [ new McpMessage(McpRole.User, $"Summarize the following contact and suggest follow-up actions:\n\n{contact}") ] };}).AsPrompt().WithDescription("Generate a summary prompt for a contact.");MCP Apps
Section titled “MCP Apps”MCP Apps are HTML surfaces rendered inside capable clients (e.g., Claude). The same Map(...) call serves launcher text for tool invocation and HTML for resources/read:
using System.Net; // WebUtility.HtmlEncode
app.Map("contacts dashboard", (IContactStore store) =>{ var contacts = store.All().ToList(); return $$""" <!DOCTYPE html> <html> <head><title>Contacts ({{contacts.Count}})</title></head> <body> <h1>Contacts</h1> <ul>{{string.Join("", contacts.Select(c => $"<li><b>{WebUtility.HtmlEncode(c.Name)}</b> — {WebUtility.HtmlEncode(c.Email)}</li>"))}}</ul> </body> </html> """;}).AsMcpAppResource().WithDescription("Open the contacts dashboard.").WithMcpAppDisplayMode(McpAppDisplayMode.Inline);Configure Content Security Policy for apps that load external assets:
.WithMcpAppCsp(csp => csp.AddScriptSrc("https://cdn.myapp.io"))Interaction in MCP mode
Section titled “Interaction in MCP mode”Repl routes IReplInteractionChannel calls through MCP primitives when running as an MCP server. Configure the fallback when a client doesn’t support elicitation:
app.UseMcpServer(o =>{ o.InteractivityMode = InteractivityMode.PrefillThenElicitation; // PrefillThenFail — fail if no prefill provided (default) // PrefillThenDefaults — use C# parameter defaults // PrefillThenElicitation — structured form via agent // PrefillThenSampling — LLM generates answer});Design for agents: prefer accepting all required data as route parameters or named options. Reserve IReplInteractionChannel prompts for the human-interactive experience — agents should pass everything upfront.
For direct programmatic access to sampling and elicitation (outside of the interactivity pipeline), see MCP — Advanced.
Rich tool descriptions
Section titled “Rich tool descriptions”Tools can carry extended Markdown documentation for the agent, separate from the human-facing --help text:
app.Map("contacts add {name} {email:email}", handler) .WithDescription("Add a new contact.") .WithDetails(""" Creates a new contact record with the given name and email.
**Rules:** - Email must be unique across all contacts. - Name cannot exceed 200 characters. - Returns the new contact's ID on success.
**Example:** `contacts_add` with `name="Alice Dupont"`, `email="alice@example.com"` """);WithDetails(...) content appears in the MCP tool schema but not in --help output.
Controlling tool visibility
Section titled “Controlling tool visibility”Hide individual commands from MCP:
app.Map("debug reset", handler) .AutomationHidden(); // hidden from MCP, visible in interactive REPLFilter at the server level:
app.UseMcpServer(o =>{ o.CommandFilter = cmd => !cmd.Tags.Contains("internal");});Use module presence predicates for context-sensitive visibility:
app.MapModule( new AdminModule(), (IAuthService auth) => auth.IsAdmin());To scope visibility to workspace roots (native MCP roots or soft-root fallbacks), see Client roots and soft roots.
Full configuration reference
Section titled “Full configuration reference”app.UseMcpServer(o =>{ o.ServerName = "MyApp"; o.ToolNamingSeparator = ToolNamingSeparator.Underscore; o.InteractivityMode = InteractivityMode.PrefillThenElicitation; o.ResourceFallbackToTools = true; // resources also callable as tools (compatibility fallback) o.PromptFallbackToTools = true; // AsPrompt() tools also callable as tools o.CommandFilter = cmd => true; o.DynamicToolCompatibility = DynamicToolCompatibilityMode.Disabled; // → see MCP — Advanced for shim mode});Best practices
Section titled “Best practices”Design for agents first. Commands that work well for agents work well for humans too. The reverse is not always true.
Use typed constraints. Route constraints generate typed JSON Schema properties — {id:int} becomes "type": "integer" in the tool schema. An agent that knows the type can fill parameters correctly without guessing.
One concern per tool. A command named contacts_sync_import_and_notify is hard for an agent to reason about. Prefer contacts_import and contacts_notify as separate tools.
Annotate everything destructive. Missing .Destructive() on a delete command may cause an agent to call it without warning. When in doubt, annotate conservatively.
Keep tool names short. Agents have context windows. contacts_list is better than contact_management_list_all_records. Prefer short, noun-verb names.
Test with MCP Inspector. Run myapp mcp serve and point MCP Inspector at it to verify tool schemas, annotations, and resources before connecting a real agent.
Going further
Section titled “Going further”For advanced topics — programmatic sampling and elicitation from handler code, client roots and soft roots, custom transports, and HTTP hosting — see MCP — Advanced.