About FsMcp
FsMcp is an MCP server in the AI category: fsMcp is an idiomatic F# toolkit for building Model Context Protocol (MCP) servers and clients with type safety, computation expressions, and zero boilerplate. It has been installed 0 times through Conduid.
Install
git clone https://github.com/Neftedollar/FsMcpThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about FsMcp
Powered by Claude · Grounded in docs
Security checks
- ·README presentNot checked yet.
- ·License declaredNot checked yet.
- ·Tests presentNot checked yet.
- ·Dependencies pinnedNot checked yet.
- ·No dynamic code executionNot checked yet.
- ·Scoped permissionsNot checked yet.
README
FsMcp
FsMcp is an idiomatic F# toolkit for building Model Context Protocol (MCP) servers and clients. It wraps the official Microsoft ModelContextProtocol .NET SDK with computation expressions, typed tool handlers, Result-based error handling, and composable middleware — so you can build MCP servers in F# with type safety and zero boilerplate.
type GreetArgs = { name: string; greeting: string option }
let server = mcpServer {
name "MyServer"
version "1.0.0"
tool (TypedTool.define<GreetArgs> "greet" "Greets a person" (fun args -> task {
let greeting = args.greeting |> Option.defaultValue "Hello"
return Ok [ Content.text $"{greeting}, {args.name}!" ]
}) |> unwrapResult)
useStdio
}
Server.run server |> fun t -> t.GetAwaiter().GetResult()
// Input schema auto-generated: name=required, greeting=optional
Install
dotnet add package FsMcp.Server # server builder + stdio transport
dotnet add package FsMcp.Client # typed client wrapper
dotnet add package FsMcp.Testing # test helpers + FsCheck generators
dotnet add package FsMcp.TaskApi # FsToolkit.ErrorHandling pipeline
dotnet add package FsMcp.Server.Http # HTTP/SSE transport (opt-in ASP.NET)
dotnet add package FsMcp.Sampling # LLM sampling from server tools
Why FsMcp?
mcpServer { }CE — declare tools, resources, prompts in a single blockTypedTool.define<'T>— F# record as input, JSON Schema auto-generated via TypeShapeResult<'T, McpError>— no exceptions in expected paths, typed errors everywhere- Smart constructors —
ToolName.createvalidates at construction, not at runtime - Composable middleware — logging, validation, telemetry via
Middleware.pipeline - 322 tests — Expecto + FsCheck property tests on every domain type
Quick Start
Server with typed tools
open FsMcp.Core
open FsMcp.Core.Validation
open FsMcp.Server
type CalcArgs = { a: float; b: float }
let server = mcpServer {
name "Calculator"
version "1.0.0"
tool (TypedTool.define<CalcArgs> "add" "Add two numbers" (fun args -> task {
return Ok [ Content.text $"{args.a + args.b}" ]
}) |> unwrapResult)
tool (TypedTool.define<CalcArgs> "divide" "Divide a by b" (fun args -> task {
if args.b = 0.0 then return Error (TransportError "Division by zero")
else return Ok [ Content.text $"{args.a / args.b}" ]
}) |> unwrapResult)
useStdio
}
Server.run server |> fun t -> t.GetAwaiter().GetResult()
HTTP transport
dotnet add package FsMcp.Server.Http
open FsMcp.Server.Http
HttpServer.run server (Some "/mcp") "http://localhost:3001"
|> fun t -> t.GetAwaiter().GetResult()
Client
open FsMcp.Core.Validation
open FsMcp.Client
let demo () = task {
let config = {
Transport = ClientTransport.stdio "dotnet" ["run"; "--project"; "../Calculator"]
Name = "TestClient"
ShutdownTimeout = None
}
let! client = McpClient.connect config
let! tools = McpClient.listTools client
let toolName = ToolName.create "add" |> unwrapResult
let args = Map.ofList [
"a", System.Text.Json.JsonDocument.Parse("10").RootElement
"b", System.Text.Json.JsonDocument.Parse("20").RootElement
]
let! result = McpClient.callTool client toolName args
// result : Result<Content list, McpError>
}
Testing
open FsMcp.Testing
// Direct handler invocation — no network, no process spawning
let result =
TestServer.callTool serverConfig "add"
(Map.ofList ["a", jsonEl 10; "b", jsonEl 20])
|> Async.AwaitTask |> Async.RunSynchronously
result |> Expect.mcpHasTextContent "30" "addition works"
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Your F# Code │
│ mcpServer { tool ...; resource ...; prompt ... } │
├──────────────┬──────────────────────────────┬───────────────────┤
│ FsMcp.Server │ FsMcp.Core │ FsMcp.Client │
│ │ │ │
│ CE builder Types (DUs, records) │ Typed wrapper │
│ TypedHandlers Validation (smart ctors) │ Async module │
│ Middleware Serialization (JSON) │ │
│ Streaming Interop (internal) │ │
│ Telemetry │ │
├──────────────┴──────────────────────────────┴───────────────────┤
│ Microsoft ModelContextProtocol SDK │
├─────────────────────────────────────────────────────────────────┤
│ .NET 10 Runtime │
└─────────────────────────────────────────────────────────────────┘
Packages
| Package | What it does |
|---|---|
| FsMcp.Core | Domain types, smart constructors, JSON serialization |
| FsMcp.Server | mcpServer { } CE, typed handlers, middleware, stdio transport |
| FsMcp.Server.Http | HTTP/SSE transport via ASP.NET Core (opt-in) |
| FsMcp.Client | Typed client with Result<'T, McpError> |
| FsMcp.Testing | TestServer.callTool, Expect.mcp*, FsCheck generators |
| FsMcp.TaskApi | taskResult { } pipeline via FsToolkit.ErrorHandling |
| FsMcp.Sampling | Server-side LLM invocation via MCP sampling |
Features
- Typed tool handlers —
TypedTool.define<'T>with TypeShape-powered JSON Schema + caching - Nested CE —
mcpTool { toolName "..."; typedHandler ... } - Streaming tools —
StreamingTool.definewithIAsyncEnumerable<Content> - Notifications —
ContextualTool.definewith progress + log callbacks - Validation middleware — auto-validates args against schema before handler
- Telemetry —
Telemetry.tracing()(Activity/OTel) +MetricsCollector - Hot reload —
DynamicServer.addTool/removeToolat runtime - Error handling —
FsToolkit.ErrorHandlingintegration viaFsMcp.TaskApi
Build & Test
dotnet build # 7 packages
dotnet test # 322 tests (Expecto + FsCheck)
Runtime tuning for stdio servers
By default .NET runs the Server GC, which is throughput-optimized and does not proactively return committed heap pages to the OS. For an idle stdio MCP server this can look like a memory leak — RSS grows during a session and stays elevated even when the server is quiet. The runtime releases the memory immediately once the OS signals memory pressure, confirming it was commit-grow, not a genuine leak.
Set these environment variables to reduce idle RSS:
DOTNET_gcServer=0 # Workstation GC — returns memory at idle
DOTNET_gcConcurrent=1 # Concurrent collection — shorter pauses
See docs/runtime-tuning.md for the full explanation, MCP client config examples (Claude Code, Codex), a runtimeconfig.template.json snippet for redistributable tools, and a five-minute diagnostic recipe to distinguish commit-grow from an actual leak.
Examples
See examples/ for runnable MCP servers:
- EchoServer — echo + reverse tools, resource, prompt
- Calculator — add/subtract/multiply/divide
- FileServer — read_file, list_directory, file_info
Design Principles
- Wrap, don't reimplement — protocol concerns stay in Microsoft SDK
- Idiomatic F# — DUs, Result, CEs, pipe-friendly
- Type safety — private constructors, no
objin public API - Test-first — Expecto + FsCheck on every function
- Composable — middleware, function handlers, no inheritance
Contributing
See CONTRIBUTING.md. Issues and PRs welcome.
License
README mirrored from the source repository 4 months ago. The original is authoritative.