Dignite.Abp.AspNetCore.Mcp
10.7.0
dotnet add package Dignite.Abp.AspNetCore.Mcp --version 10.7.0
NuGet\Install-Package Dignite.Abp.AspNetCore.Mcp -Version 10.7.0
<PackageReference Include="Dignite.Abp.AspNetCore.Mcp" Version="10.7.0" />
<PackageVersion Include="Dignite.Abp.AspNetCore.Mcp" Version="10.7.0" />
<PackageReference Include="Dignite.Abp.AspNetCore.Mcp" />
paket add Dignite.Abp.AspNetCore.Mcp --version 10.7.0
#r "nuget: Dignite.Abp.AspNetCore.Mcp, 10.7.0"
#:package Dignite.Abp.AspNetCore.Mcp@10.7.0
#addin nuget:?package=Dignite.Abp.AspNetCore.Mcp&version=10.7.0
#tool nuget:?package=Dignite.Abp.AspNetCore.Mcp&version=10.7.0
Dignite.Abp.AspNetCore.Mcp
Part of dignite-projects/abp-modules — see the repository README for the other modules, and CONTRIBUTING.md for the build, versioning, and release process shared across them.
Hosts one Model Context Protocol (MCP) server inside an ABP Framework application, and lets any number of ABP modules contribute tools, resources and prompts to it. It is infrastructure, not a module with a domain model of its own: it does not reference, and is not owned by, any module in this repository.
.NET 10 · ABP 10.7.0 · ModelContextProtocol 2.1.0 · LGPL-3.0-only
Why it exists
The C# MCP SDK keeps exactly one server per service container. Every module's WithTools<T>()
lands in the same server, while the server info, the instructions, the transport, the endpoint and every
request filter exist once. A feature module that configures any of those is configuring the whole
server, and the second such module collides with it. On top of that, the SDK resolves name collisions
silently: a second tool with an existing name, or a second resource with an existing URI template, is
dropped without an error, and which one survives depends on module load order.
This package splits the two roles the way ABP does for SignalR (AbpAspNetCoreSignalRModule maps the
hubs; feature modules only supply them):
| Owns | Never touches | |
|---|---|---|
AbpAspNetCoreMcpModule (this package) |
The server, stateless Streamable HTTP transport, the /mcp endpoint, ABP-permission filtering of list results, a unit of work per call, one structured error envelope, server info, composed instructions, startup validation, optional RFC 9728 discovery |
Any business concept |
A feature module's *.Mcp project |
Its tool/resource/prompt classes, its namespace, its section of the instructions | Server info, transport, endpoint, global filters, server-wide With*Handler slots |
| The host | Deployment: route, server name, the bearer scheme, discovery metadata, rate limiting | MCP server code |
Contributing to the server (a feature module)
[DependsOn(typeof(MyApplicationContractsModule), typeof(AbpAspNetCoreMcpModule))]
public class MyMcpModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpMcpModule("my_module", mcp => mcp
.AddTools<MyTools>()
.AddResources<MyResources>()
.AddInstructions("What my_module_* tools are for, and how to start."));
}
}
Tool classes use the SDK's own attributes ([McpServerToolType], [McpServerTool], [Description])
and ASP.NET Core's [Authorize(MyPermissions.X)], which ABP resolves as a permission. A tool should call
the module's application services — it is another caller of them, not a second implementation.
Namespaces — how names stay unique
AddAbpMcpModule(name, …) claims a namespace, and the server refuses to start if anything breaks
it:
- every tool and prompt name must start with
{name}_(my_module_list_items); - every resource URI must use the module's scheme — by default the name with
_turned into-(my-module://…), or set one withUseUriScheme(...); - module names are lower-case snake_case and must not nest (
vaultandvault_extractcannot coexist); - two modules may not share a URI scheme;
- a tool, resource or prompt registered outside
AddAbpMcpModule(the SDK's rawWithTools<T>()) is rejected, since no namespace owns it - unless the host setsAbpMcpServerOptions.AllowUnownedPrimitives, the way to host a third-party MCP tool library that cannot be changed (duplicates are refused either way); - any duplicate name or URI template fails, naming both registrations.
Because the rules are checked against a module's own registrations, a module's tests catch a violation before any host combines it with another module.
Registered namespaces in this ecosystem: site (Dignite.Site), vault_extract (Dignite.Vault.Extract,
scheme vault-extract).
Dynamic resources in resources/list
Static [McpServerResource] registrations are listed by the SDK itself. For resources that are the
caller's data (one entry per document type, per cabinet…), implement IAbpMcpResourceListContributor and
register it with AddResourceListContributor<T>(). Do not use the SDK's WithListResourcesHandler: it
is one slot on the shared server, and the next module to set it replaces yours. Contributors are resolved
from the request scope, must authorize what they list, return null/empty rather than throw when the
caller may see nothing (an exception fails the listing for every module), and may only list URIs in their
module's scheme.
Why AddTools<T>() rather than the SDK's WithTools<T>()
Besides recording ownership, it resolves the tool class from ABP's container on every call. The SDK
constructs it with ActivatorUtilities instead, so a replacement registered with
[Dependency(ReplaceServices = true)] is never used and overriding a virtual tool method silently does
nothing.
Hosting the server (an application)
Depend on the feature modules you want; this module comes with them. Then configure deployment:
Configure<AbpMcpServerOptions>(options =>
{
options.ServerName = "My Application"; // default: the ABP application name
options.Instructions = "This server manages ..."; // optional opening section
options.MaxRequestBodySize = 4 * 1024 * 1024; // the default; modules may raise it for themselves
options.EndpointConventions.Add(endpoint => endpoint.RequireRateLimiting("mcp"));
});
// Lets an MCP client without a token discover where to authenticate (RFC 9728):
context.Services.AddAbpMcpAuthenticationDiscovery(metadata =>
{
metadata.Resource = configuration["App:SelfUrl"];
metadata.AuthorizationServers.Add(configuration["AuthServer:Authority"]);
metadata.ScopesSupported.Add("MyApp");
});
The pipeline needs UseRouting(), UseAuthentication(), UseAuthorization() and
UseConfiguredEndpoints(), as any ABP host has.
Authentication is the host's existing bearer scheme, through the endpoint's default policy. The
endpoint deliberately does not name an authentication scheme: naming one makes ASP.NET Core
re-authenticate through it and replace the principal that ABP's dynamic claims middleware has already
refreshed, so role changes and revocations made after a token was issued would not reach MCP requests.
AddAbpMcpAuthenticationDiscovery therefore only takes over the challenge — an unauthenticated MCP
request gets 401 with WWW-Authenticate: Bearer resource_metadata="…" instead of the host's default
(in an ABP MVC host, a 302 to the login page that no MCP client can follow).
It does that by wrapping the host's IAuthorizationMiddlewareResultHandler (keeping its lifetime), so a
host with a handler of its own registers it before calling AddAbpMcpAuthenticationDiscovery; one
registered afterwards would replace the wrapper, and the application refuses to start rather than let the
MCP 401 silently turn back into a login redirect. Calling it again only replaces the metadata.
Errors
Any exception a tool throws becomes a CallToolResult with isError: true and a structured payload,
built with ABP's own IExceptionToErrorInfoConverter:
{ "error": { "kind": "validation", "code": null, "message": "…",
"validationErrors": [ { "message": "…", "fields": [ "slug" ] } ] } }
kind is one of validation, notFound, conflict, forbidden, unknown. An exception whose type
would be classified wrongly — typically a UserFriendlyException that means "not found" — implements
IHasMcpToolErrorKind to say so.
The SDK's own exceptions keep the SDK's semantics: an McpException's message is passed through as
written (ABP's converter would replace it with a generic one), while McpProtocolException (a
protocol-level failure, such as an unknown tool) and InputRequiredException (a tool asking the client for
more input mid-call) are not converted at all.
Every exception reported this way is also logged - and passed to IExceptionNotifier - exactly as
ABP's AbpExceptionHandlingMiddleware does for the HTTP API, honouring
AbpExceptionHandlingOptions.ExcludeExceptionFromLoggerSelectors. The client of an unexpected failure
is only told that an internal error occurred, so the log is where its cause is found.
Unit of work
Each tool call, resource read and prompt runs in the unit of work an MVC action would get from ABP's
AbpUowActionFilter: it takes over the one UseUnitOfWork() reserved for the request (or begins its
own, in a host without that middleware), saves the changes before the result is returned, and rolls
back if the call fails. Without it the request's changes would only be saved once the whole pipeline
had returned - after the SDK had already written the result - so a save that fails on a unique index
or a concurrency conflict would cut off the client's connection instead of coming back as the call's
error. Tool calls are transactional by default, as a POST would be; resource reads and prompts are not,
as a GET would not be (AbpUnitOfWorkDefaultOptions decides either way). As for an MVC action,
committing the transaction itself stays with the middleware at the end of the request.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.FileProviders.Embedded (>= 10.0.12)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.12)
- ModelContextProtocol.AspNetCore (>= 2.1.0)
- Volo.Abp.AspNetCore (>= 10.7.0)
- Volo.Abp.Authorization (>= 10.7.0)
- Volo.Abp.Authorization.Abstractions (>= 10.7.0)
- Volo.Abp.Core (>= 10.7.0)
- Volo.Abp.EventBus.Abstractions (>= 10.7.0)
- Volo.Abp.Json.SystemTextJson (>= 10.7.0)
- Volo.Abp.Localization (>= 10.7.0)
- Volo.Abp.MultiTenancy.Abstractions (>= 10.7.0)
- Volo.Abp.Timing (>= 10.7.0)
- Volo.Abp.Validation (>= 10.7.0)
- Volo.Abp.VirtualFileSystem (>= 10.7.0)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Dignite.Abp.AspNetCore.Mcp:
| Package | Downloads |
|---|---|
|
Dignite.Vault.Extract.Mcp
MCP server exit of Dignite Vault Extract: exposes documents, document types, and cabinets as resources and provides structured metadata/extracted-field search tools for Claude Desktop, Cursor, and any MCP client. Contributes to the application's shared MCP server (Dignite.Abp.AspNetCore.Mcp) under the vault_extract namespace. |
|
|
Dignite.FileExplorer.Mcp
MCP tools for Dignite.FileExplorer: lets an AI client list the file containers a host exposes, browse directories and files, upload small files and rename, move or delete them - every call going through the File Explorer application services and their per-container authorization. Contributes to the application's MCP server through Dignite.Abp.AspNetCore.Mcp, under the file_explorer namespace. |
|
|
Dignite.Site.Mcp
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.7.0 | 154 | 10/15/2026 |
| 10.0.0-rc.29 | 79 | 10/12/2026 |
| 10.0.0-rc.28 | 63 | 10/12/2026 |
| 10.0.0-rc.27 | 51 | 10/10/2026 |
| 10.0.0-rc.26 | 53 | 10/10/2026 |
| 10.0.0-rc.25 | 61 | 10/10/2026 |
| 10.0.0-rc.24 | 58 | 10/9/2026 |
| 10.0.0-rc.23 | 58 | 10/9/2026 |
| 10.0.0-rc.22 | 59 | 10/9/2026 |
| 10.0.0-rc.21 | 58 | 10/7/2026 |
| 10.0.0-rc.20 | 63 | 10/7/2026 |
| 10.0.0-rc.19 | 115 | 10/4/2026 |
| 10.0.0-rc.18 | 70 | 10/4/2026 |