Skip to content

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.

using Repl.Mcp;
app.UseMcpServer();

Start with:

Terminal window
myapp mcp serve

Configure 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");

Expose read-only data as MCP resources:

app.Map("contacts export", static (IContactStore store) => store.All())
.AsResource()
.WithDescription("All contacts as a JSON resource");

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");

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.

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");
AnnotationMeaning
.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

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.

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);
});

  • 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.