MCP Server
Repl.Mcp turns your entire command graph into an MCP server. Every Map(...) becomes a tool; return values, resources, and prompts are all first-class.
Enable the MCP server
Section titled “Enable the MCP server”using Repl.Mcp;
app.UseMcpServer();Start with:
myapp mcp serveConfigure in your agent (Claude Desktop, Cursor, etc.):
{ "mcpServers": { "myapp": { "command": "myapp", "args": ["mcp", "serve"] } }}Every Map(...) becomes an MCP tool automatically. The tool name is the route, description comes from [Description] or .WithDescription(...).
app.Map("contacts list", static (IContactStore store) => store.All()) .WithDescription("List all contacts");
app.Map("contacts add {name} {email:email}", static (string name, string email, IContactStore store) =>{ store.Add(name, email); return Results.Success($"Added {name}.");}).WithDescription("Add a new contact");Resources
Section titled “Resources”Expose read-only data as MCP resources:
app.Map("contacts export", static (IContactStore store) => store.All()) .AsResource() .WithDescription("All contacts as a JSON resource");Prompts
Section titled “Prompts”Register reusable prompt templates:
app.Map("prompts summarize {contact_id:int}", static (int contactId, IContactStore store) =>{ var contact = store.Get(contactId); return new McpPromptResult { Messages = [new McpMessage(McpRole.User, $"Summarize the following contact: {contact}")] };}).AsPrompt().WithDescription("Generate a summary prompt for a contact");MCP Apps
Section titled “MCP Apps”Return HTML from a command and register it as an MCP App resource — the client renders it as an interactive panel:
using System.Net; // WebUtility.HtmlEncode
app.Map("contacts dashboard", static (IContactStore store) =>{ var contacts = store.All().ToList(); return $$""" <html> <body> <h1>Contacts ({{contacts.Count}})</h1> <ul>{{string.Join("", contacts.Select(c => $"<li>{WebUtility.HtmlEncode(c.Name)}</li>"))}}</ul> </body> </html> """;}).AsMcpAppResource().WithDescription("Open the contacts dashboard");Always HTML-encode user-controlled values. Raw interpolation of contact data into HTML creates stored XSS — an attacker who controls the data can execute scripts in the MCP client’s WebView.
Behavioral annotations
Section titled “Behavioral annotations”Tell the agent how it should handle your tools:
app.Map("contacts delete {id:int}", static (int id, IContactStore store) =>{ store.Delete(id); return Results.Success("Contact deleted.");}).Destructive().WithDescription("Delete a contact permanently");| Annotation | Meaning |
|---|---|
.Destructive() | Agent should warn before calling |
.ReadOnly() | No side effects — call freely, parallelize |
.Idempotent() | Safe to retry on transient failure |
.OpenWorld() | Reaches external systems (email, HTTP, etc.) |
.LongRunning() | Enable call-now/poll-later pattern |
Automation visibility
Section titled “Automation visibility”Some commands are useful for humans in REPL but should be hidden from agents:
app.Map("debug reset", static (IContactStore store) => store.Clear()) .AutomationHidden();.AutomationHidden() hides the command from MCP and other programmatic surfaces while keeping it accessible in the interactive REPL and --help.
Client roots
Section titled “Client roots”Expose the client’s file system roots to your tools:
app.Map("analyze {path}", static async (string path, IMcpClientRoots roots) =>{ if (!roots.IsAllowed(path)) return Results.Error("outside_root", $"Path '{path}' is not within an allowed root.");
return await AnalyzeFile(path);});Further reading
Section titled “Further reading”- MCP Concepts — tool annotations, resources, prompt templates, client roots, and the full MCP protocol integration model.
- Agent-Native Design — designing commands for AI agents: naming, descriptions, idempotency, and output contracts.
- Best Practices — output correctness, logging vs output separation, and security considerations for exposed commands.