How I Built a C# MCP Server with Single-Use Order Approvals A developer built dotnet-mcp-orders-server, a .NET 8 MCP server exposing fictional order list, lookup and cancellation tools over stdio, in which cancellation requires an independently recorded, single-use, expiring approval rather than a model-set flag. The approval store claims each approval file by atomic rename before validating action, order ID and expiration, failing closed on malformed or missing approvals, and the project's 12 tests pass in GitHub CI though they do not cover a live client connection. Giving an assistant a tool for reading orders is straightforward. Giving it a cancellation tool raises a different question: who authorizes the change? I wanted to explore that boundary without treating a model-generated confirmation as permission. I built dotnet-mcp-orders-server https://github.com/martinPisacreta/dotnet-mcp-orders-server , a small .NET 8 MCP server exposing fictional orders. It offers list, lookup and cancellation tools over stdio. Cancellation requires an independently recorded approval for one action and one exact order, with an expiration time. The project has 12 tests, all passing in GitHub CI. Those tests exercise the tool methods and approval behavior; they do not establish an interactive connection with Claude or ChatGPT. Here is how I built the approval flow and where its guarantees end. I expose three tools: list orders , get order and cancel order . The first two read seeded, in-memory data. The third changes a pending order to cancelled. No external order system or payment provider is involved. The cancellation tool accepts only an order ID. There is no approved boolean for the assistant to set, and no tool for creating approvals. This is the declaration from OrderTools.cs : McpServerTool Name = "cancel order" Description "Cancel a pending fictional order only when an operator has independently recorded an unexpired, single-use approval. This tool cannot create approvals." public Order CancelOrder Description "Exact ID of the order approved for cancellation." string orderId = Explain = orders.Cancel orderId ; The description helps the assistant understand the contract. Enforcement lives in the service and approval store, so it still applies if a client ignores that description. I use the MCP SDK's stdio transport and register the tool class with dependency injection. Application logs go to stderr because stdout is reserved for protocol messages. Mixing ordinary console output into that stream can break communication before business logic even runs. An operator runs the repository's separate Python approval command, supplying the approval directory and order ID. The command writes an approval containing cancel order , the exact order ID and an expiration five minutes in the future. It writes a temporary file in the destination directory, flushes it, and replaces the final JSON file. That avoids publishing partially written JSON during an ordinary update. I would not turn that helper into another model-accessible tool: doing so would let the caller manufacture the permission it needs. The server reads its approval directory from ORDERS APPROVAL DIRECTORY , falling back to a directory beside the assembly. In a real setup, choosing that location and controlling who can write there are part of the authorization design. A path alone authenticates nobody. Reading an approval and deleting it afterward would leave a race: two callers could both read the same permission before either removes it. My store first renames the file to a unique claimed name. This excerpt from OrderService.cs shows the claim and validation: js File.Move source, claimed ; moved = true; var approval = JsonSerializer.Deserialize