How to Implement MCP Tools, Resources and Prompts in ASP.NET Core 10 Using C# – Complete Guide (2026)
How to Implement MCP Tools, Resources and Prompts in ASP.NET Core 10 Using C# – Complete Guide (2026)
A developer documentation assistant needs three different capabilities. It needs to search the handbook, read a specific article, and prepare a consistent summary request. MCP gives each of these jobs its own contract. A C# server can expose all three over a single Streamable HTTP endpoint, while a client can test the complete workflow without calling a model.
This tutorial builds that server in ASP.NET Core 10 using the official MCP C# SDK 2.3.1. You will register a structured search tool, a fixed resource, an RFC 6570 resource template, and a prompt with two arguments. A console client lists, calls, reads and retrieves them against the same fixture.
What are MCP tools, resources and prompts?
MCP tools are callable operations. A tool can run a search, perform a calculation or invoke an application service. It describes its input with JSON Schema and can return content blocks and structured data. In this tutorial, search_docs searches titles and short descriptions and returns stable document URIs.
MCP resources are readable data addressed by URI. They can represent documents, files, or records. An application reads a URI it already knows or discovers, such as docs://handbook/articles/timeouts. The resource URI is interpreted by the MCP server that exposes it. It is an identifier, and it need not be an HTTP URL.
MCP prompts are reusable message templates. A client requests one by name and supplies arguments such as a document ID and audience. The server returns formatted messages. The host may present those messages to a user or include them in a model request. Retrieving a prompt does not itself generate a summary.
| Capability | Choose it when | Discover | Execute or retrieve |
|---|---|---|---|
| Tool | An operation needs input and computes a result or performs an action | tools/list |
tools/call |
| Fixed resource | The application can read data at a known URI | resources/list |
resources/read |
| Resource template | A family of resources shares a parameterized URI | resources/templates/list |
Expand the URI, then use resources/read |
| Prompt | A repeatable workflow needs messages shaped by user input | prompts/list |
prompts/get |
When should you use a tool instead of a resource? Use a tool when the caller asks the server to perform an operation, such as finding matching documents for a new query. Use a resource when the client has a stable identifier and wants the associated data. Read-only tools are valid. The distinction is the interaction contract. In the sample, a search result supplies an article URI that the application can choose to read next.
See the official specifications for tools, resources and prompts.
How the documentation assistant works
The server has a small, immutable in-memory catalog and no database or model dependencies. Three registration calls expose capabilities backed by that catalog. The console application controls sequencing. It reads the welcome resource, searches for timeout, reads the matched article and retrieves a summary prompt for a beginner. The article text stays separate from the template so the host can decide what context to send to a model.
The gateway in a production application can authenticate a person or service, call a trusted MCP endpoint, apply user and tenant policy, and decide which results can enter a model context. A model is optional for MCP discovery and execution. For a server-first introduction, see the ASP.NET PB MCP server tutorial.
Which protocol version and transport should you use?
The guide targets the published 2026-07-28 specification. Modern requests carry protocol and client metadata, and server/discover can describe the server’s capabilities. With SDK v2 defaults, the client probes for modern discovery and can fall back to the earlier initialize handshake when compatibility requires it. Older peers exchange version and capabilities during initialization. The server’s advertised capabilities tell a client which operation groups it can attempt. They do not give a caller permission.
This lab uses Streamable HTTP. Use the exact /mcp endpoint. An ordinary browser GET is not the way to call tools/list. The modern HTTP binding supports POST requests and may return JSON or streamed responses. Older session behavior and HTTP+SSE compatibility depend on the negotiated revision and configuration. See versioning and the SDK transport guide.
Create the ASP.NET Core 10 server and .NET client
Install a .NET 10 SDK, choose a C# editor and create two sibling projects. The server depends on ModelContextProtocol.AspNetCore for ASP.NET Core integration. The client needs ModelContextProtocol.Core for transport and client APIs. Both package versions used here are 2.3.1 and the projects target net10.0.
Add the files below to the named projects. They use the implicit usings enabled by the .NET templates. See the SDK release notes, server package and client package.
How do you register MCP capabilities in C#?
Call AddMcpServer() on the service collection, configure WithHttpTransport(), and use WithTools<T>(), WithResources<T>() and WithPrompts<T>() for the selected types. Map the resulting server at /mcp. Explicit registration makes it easy to review what becomes discoverable. The SDK also supports assembly scanning for attributed types.
Replace the server’s Program.cs and appsettings.json. The loopback test uses only synthetic public text. The host list restricts expected host names and the sample denies requests carrying an Origin header because the test uses a native console client. This check is not caller authentication. Bind to loopback as shown. If you need a browser client, configure intentional Origin and CORS policies. For remote use, add HTTPS, authenticated callers and appropriate access policies.
Register the catalog as a singleton because its fixture data does not mutate. If you replace it with a database, use an appropriate request or operation lifetime and propagate the cancellation token through database calls. Keep business logic in an injected service rather than giving handlers unrestricted file-system or network access.
Implement a tool, resource template and reusable prompt
Add the complete capability definitions to DocsMcp/DocCapabilities.cs. The SDK injects DocCatalog and the request cancellation token. The consumer supplies the documented search, resource or prompt parameters.
How does MCP tool input validation work?
The SDK derives search_docs input schema from the C# signature. The observed schema has a required string query and an integer limit with a default of 3. The descriptions tell the caller the accepted lengths and range. In this SDK output those constraints appear as description text. The server explicitly enforces them in code.
ReadOnly = true is a tool annotation for consumers. The handler is read-only because the implementation performs no mutation. Treat annotations as descriptive hints. Use verified identity and policy checks for authorization. A tool can report execution errors through isError. Transport failures and JSON-RPC errors require separate handling in the client.
How do you expose and read MCP resources?
Use a fixed UriTemplate value for a resource that always has the same address. Use placeholders for a family of resources. This sample exposes the fixed welcome URI through resources/list and the article pattern through resources/templates/list. For a template, the client supplies an expanded URI such as docs://handbook/articles/timeouts to resources/read.
DocCatalog.Find accepts only existing catalog identifiers. It never combines an input ID with a disk path or fetches a remote URL. That keeps both path traversal and outbound fetches out of the example. The returned content declares text/plain. For large documents, define size limits and a chunking or paging strategy appropriate to the client.
How do MCP clients discover and use prompts?
Call prompts/list for the prompt names and argument descriptions. Call prompts/get with the name and string arguments. summarize_doc requires an existing article ID and accepts one of two audiences. It returns a user-role message describing the task. It does not include the body or call an LLM. The host decides whether to pair that message with the separately read resource.
Review prompt templates and resource text as untrusted input before supplying them to a model. Prompt wording cannot enforce security policy. A client should keep system instructions, user intent and retrieved content distinguishable and enforce tool permissions at the action boundary. See the SDK documentation for tool schemas, resource templates and prompt arguments.
Run the server and verify all capabilities
Replace DocsMcp.Client/Program.cs with the complete client below. It uses one 15-second cancellation budget for the sample run. The single-page list calls are sufficient for this catalog. Larger clients should follow cursors or use the SDK’s suitable aggregation overloads within a resource budget.
In terminal one, run the server from the parent directory. In terminal two, run the client. There is no browser login, model API key or database to configure.
The console client sends the equivalent of these operation arguments. The SDK wraps them in JSON-RPC and supplies the necessary transport and protocol metadata.
| Operation | Arguments |
|---|---|
tools/call |
name=search_docs, arguments={"query":"timeout","limit":1} |
resources/read |
uri=docs://handbook/welcome |
resources/read |
uri=docs://handbook/articles/timeouts |
prompts/get |
name=summarize_doc, arguments={"id":"timeouts","audience":"beginner"} |
The observed search payload is shown below, formatted for readability. After reading the returned URI, the client prints the article’s cancellation guidance. Prompt retrieval returns a message asking for three bullets targeted at a beginner developer.
Execution record. Both projects compiled without warnings using .NET SDK 10.0.100 and MCP C# SDK 2.3.1. The console runner exercised Streamable HTTP discovery for tools, fixed resources, resource templates and prompts, then read both resources, ran the search and retrieved the summary prompt. It rejected a one-character query, an unknown article and an unsupported prompt audience. A separate HTTP probe with an untrusted Origin received 403. The fixture was tested on loopback. Browser Inspector, a deployed endpoint and model generation were outside the execution run.
You can also use a compatible MCP Inspector release to explore discovery and invoke tools. Choose Streamable HTTP and the full loopback URL. If a browser-based client sends Origin directly to this console-only fixture, it should receive 403. Use the C# runner or configure an explicit browser-origin policy for a separate test deployment. See the official MCP Inspector repository and SDK client reference.
Secure and operate the capability boundary
- Scope access per operation. For authenticated deployments, apply endpoint and capability policies for tools, prompts and resources. A list response should reflect what the caller may discover, and execution must enforce permissions again. Include object and tenant checks for resource reads. A predictable URI is never a permission.
- Treat model-selected actions as requests. Validate structured arguments and require confirmation for sensitive operations where appropriate. Bound execution time and output size. Use idempotency or reconciliation for writes and avoid retrying blindly after a timeout.
- Keep sensitive data out of discovery. Descriptions and example arguments are visible to clients with discovery access. Keep private data in authorized resources and log operation names, duration, outcomes and correlation IDs. Redact content, tokens and secrets.
- Design pagination and caching around identity. Separate immutable public catalog metadata from per-user data. Respect cache guidance and invalidation where supported. Avoid reusing discovery results across identities when access can differ.
- Keep the host in charge of context. Let the host decide whether to retrieve a URI and which snippets are appropriate for a model. Avoid automatic fetches of arbitrary URI schemes or origins.
See the MCP authorization specification. For related ASP.NET PB examples, see JWT authentication and the RAG chatbot tutorial.
Troubleshoot common MCP capability errors
| Symptom | Check |
|---|---|
| The tool is absent from discovery | Check the tool attribute, type registration, endpoint, and the caller’s access policy. |
| The schema contains service parameters | Verify service registration and that the SDK recognizes the injected parameter type. Inspect tools/list after each contract change. |
| The schema shows descriptions but no numeric bounds | Verify what your pinned SDK actually emits. Keep runtime range checks independent of schema display. |
The article template is absent from resources/list |
Use resources/templates/list. It is a separate discovery operation. |
| The URI looks valid but read fails | Expand the template, check the exact ID and verify that the resource belongs to the authorized catalog. |
| Getting a prompt produces no final answer | Inspect the returned messages. A separate host or model call is needed to generate an answer. |
| An HTTP request returns 403 from this lab | Check whether the client sent an Origin header. Use the native runner or a separately configured browser test. |
| The server and client disagree about transport | Confirm the /mcp URL, the Streamable HTTP mode and the SDK/protocol compatibility settings. |
Frequently asked questions
Can an MCP tool return resource links?
Yes. A tool can return resource-link content blocks when appropriate. This sample deliberately returns URIs as fields in structured data. That contract works for this client, but it is distinct from a protocol-level resource link block.
What is the difference between an MCP resource and a REST endpoint?
A resource is identified by an MCP URI and read through the MCP server. It can be backed by a REST endpoint, database row or file. The client uses the resource read operation even when the server uses a different storage interface internally.
Can I use a C# MCP prompt without an AI model?
Yes. A client can list and retrieve prompt messages and display or test them without invoking a model. A host that wants a generated response must make that separate call.
Should I expose the same data through a tool and a resource?
Expose separate contracts when they help the workflow. In this example, the tool finds documents and the resource reads one identified document. Sharing a catalog behind both avoids duplicating authorization, lookup and data access rules.
Conclusion and further reading
Start by proving the smallest end-to-end path. Discover search_docs, search by keyword, read a result URI, and retrieve summarize_doc. Add a model only after the protocol contract and failure behavior are clear. Future topics can cover versioned resource IDs, capability contract tests across SDK upgrades, and tenant-aware document retrieval.
Comments
Post a Comment