Split configuration of request and message filters (#1308)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
27a3196c7f
commit
d21aa9e5a2
21 changed files with 1288 additions and 1078 deletions
|
|
@ -1,22 +1,22 @@
|
||||||
---
|
---
|
||||||
title: Filters
|
title: Filters
|
||||||
author: halter73
|
author: halter73
|
||||||
description: MCP Server Handler Filters
|
description: MCP Server Filters
|
||||||
uid: filters
|
uid: filters
|
||||||
---
|
---
|
||||||
|
|
||||||
# MCP Server Handler Filters
|
# MCP Server Filters
|
||||||
|
|
||||||
The MCP Server provides two levels of filters for intercepting and modifying request processing:
|
The MCP Server provides two levels of filters for intercepting and modifying request processing:
|
||||||
|
|
||||||
1. **Message Filters** - Low-level filters (`AddIncomingMessageFilter`, `AddOutgoingMessageFilter`) that intercept all JSON-RPC messages before routing
|
1. **Message Filters** - Low-level filters (`AddIncomingFilter`, `AddOutgoingFilter`) configured via `WithMessageFilters(...)` that intercept all JSON-RPC messages before routing.
|
||||||
2. **Request-Specific Filters** - Handler-level filters (e.g., `AddListToolsFilter`, `AddCallToolFilter`) that target specific MCP operations
|
2. **Request-Specific Filters** - Handler-level filters (e.g., `AddListToolsFilter`, `AddCallToolFilter`) configured via `WithRequestFilters(...)` that target specific MCP operations.
|
||||||
|
|
||||||
The filters are stored in `McpServerOptions.Filters` and applied during server configuration.
|
The filters are stored in `McpServerOptions.Filters`.
|
||||||
|
|
||||||
## Available Request-Specific Filter Methods
|
## Available Request-Specific Filter Methods
|
||||||
|
|
||||||
The following filter methods are available:
|
The following request filter methods are available on `IMcpRequestFilterBuilder` inside `WithRequestFilters(...)`:
|
||||||
|
|
||||||
- `AddListResourceTemplatesFilter` - Filter for list resource templates handlers
|
- `AddListResourceTemplatesFilter` - Filter for list resource templates handlers
|
||||||
- `AddListToolsFilter` - Filter for list tools handlers
|
- `AddListToolsFilter` - Filter for list tools handlers
|
||||||
|
|
@ -32,10 +32,11 @@ The following filter methods are available:
|
||||||
|
|
||||||
## Message Filters
|
## Message Filters
|
||||||
|
|
||||||
In addition to the request-specific filters above, there are low-level message filters that intercept all JSON-RPC messages before they are routed to specific handlers:
|
In addition to the request-specific filters above, there are low-level message filters that intercept all JSON-RPC messages before they are routed to specific handlers.
|
||||||
|
Configure these on `IMcpMessageFilterBuilder` inside `WithMessageFilters(...)`:
|
||||||
|
|
||||||
- `AddIncomingMessageFilter` - Filter for all incoming JSON-RPC messages (requests and notifications)
|
- `AddIncomingFilter` - Filter for all incoming JSON-RPC messages (requests and notifications)
|
||||||
- `AddOutgoingMessageFilter` - Filter for all outgoing JSON-RPC messages (responses and notifications)
|
- `AddOutgoingFilter` - Filter for all outgoing JSON-RPC messages (responses and notifications)
|
||||||
|
|
||||||
### When to Use Message Filters
|
### When to Use Message Filters
|
||||||
|
|
||||||
|
|
@ -49,22 +50,25 @@ Message filters operate at a lower level than request-specific filters and are u
|
||||||
|
|
||||||
### Incoming Message Filter
|
### Incoming Message Filter
|
||||||
|
|
||||||
`AddIncomingMessageFilter` intercepts all incoming JSON-RPC messages before they are dispatched to request-specific handlers:
|
`AddIncomingFilter` intercepts all incoming JSON-RPC messages before they are dispatched to request-specific handlers:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
services.AddMcpServer()
|
services.AddMcpServer()
|
||||||
.AddIncomingMessageFilter(next => async (context, cancellationToken) =>
|
.WithMessageFilters(messageFilters =>
|
||||||
{
|
{
|
||||||
var logger = context.Services?.GetService<ILogger<Program>>();
|
messageFilters.AddIncomingFilter(next => async (context, cancellationToken) =>
|
||||||
|
|
||||||
// Access the raw JSON-RPC message
|
|
||||||
if (context.JsonRpcMessage is JsonRpcRequest request)
|
|
||||||
{
|
{
|
||||||
logger?.LogInformation($"Incoming request: {request.Method}");
|
var logger = context.Services?.GetService<ILogger<Program>>();
|
||||||
}
|
|
||||||
|
|
||||||
// Call next to continue processing
|
// Access the raw JSON-RPC message
|
||||||
await next(context, cancellationToken);
|
if (context.JsonRpcMessage is JsonRpcRequest request)
|
||||||
|
{
|
||||||
|
logger?.LogInformation($"Incoming request: {request.Method}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call next to continue processing
|
||||||
|
await next(context, cancellationToken);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.WithTools<MyTools>();
|
.WithTools<MyTools>();
|
||||||
```
|
```
|
||||||
|
|
@ -83,46 +87,52 @@ Inside an incoming message filter, you have access to:
|
||||||
You can skip the default handler by not calling `next`. This is useful for implementing custom protocol methods:
|
You can skip the default handler by not calling `next`. This is useful for implementing custom protocol methods:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
.AddIncomingMessageFilter(next => async (context, cancellationToken) =>
|
.WithMessageFilters(messageFilters =>
|
||||||
{
|
{
|
||||||
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == "custom/myMethod")
|
messageFilters.AddIncomingFilter(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Handle the custom method directly
|
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == "custom/myMethod")
|
||||||
var response = new JsonRpcResponse
|
|
||||||
{
|
{
|
||||||
Id = request.Id,
|
// Handle the custom method directly
|
||||||
Result = JsonSerializer.SerializeToNode(new { message = "Custom response" })
|
var response = new JsonRpcResponse
|
||||||
};
|
{
|
||||||
await context.Server.SendMessageAsync(response, cancellationToken);
|
Id = request.Id,
|
||||||
return; // Don't call next - we handled it
|
Result = JsonSerializer.SerializeToNode(new { message = "Custom response" })
|
||||||
}
|
};
|
||||||
|
await context.Server.SendMessageAsync(response, cancellationToken);
|
||||||
|
return; // Don't call next - we handled it
|
||||||
|
}
|
||||||
|
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### Outgoing Message Filter
|
### Outgoing Message Filter
|
||||||
|
|
||||||
`AddOutgoingMessageFilter` intercepts all outgoing JSON-RPC messages before they are sent to the client:
|
`AddOutgoingFilter` intercepts all outgoing JSON-RPC messages before they are sent to the client:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
services.AddMcpServer()
|
services.AddMcpServer()
|
||||||
.AddOutgoingMessageFilter(next => async (context, cancellationToken) =>
|
.WithMessageFilters(messageFilters =>
|
||||||
{
|
{
|
||||||
var logger = context.Services?.GetService<ILogger<Program>>();
|
messageFilters.AddOutgoingFilter(next => async (context, cancellationToken) =>
|
||||||
|
|
||||||
// Inspect outgoing messages
|
|
||||||
switch (context.JsonRpcMessage)
|
|
||||||
{
|
{
|
||||||
case JsonRpcResponse response:
|
var logger = context.Services?.GetService<ILogger<Program>>();
|
||||||
logger?.LogInformation($"Sending response for request {response.Id}");
|
|
||||||
break;
|
|
||||||
case JsonRpcNotification notification:
|
|
||||||
logger?.LogInformation($"Sending notification: {notification.Method}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await next(context, cancellationToken);
|
// Inspect outgoing messages
|
||||||
|
switch (context.JsonRpcMessage)
|
||||||
|
{
|
||||||
|
case JsonRpcResponse response:
|
||||||
|
logger?.LogInformation($"Sending response for request {response.Id}");
|
||||||
|
break;
|
||||||
|
case JsonRpcNotification notification:
|
||||||
|
logger?.LogInformation($"Sending notification: {notification.Method}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await next(context, cancellationToken);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.WithTools<MyTools>();
|
.WithTools<MyTools>();
|
||||||
```
|
```
|
||||||
|
|
@ -132,16 +142,19 @@ services.AddMcpServer()
|
||||||
You can suppress outgoing messages by not calling `next`:
|
You can suppress outgoing messages by not calling `next`:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
.AddOutgoingMessageFilter(next => async (context, cancellationToken) =>
|
.WithMessageFilters(messageFilters =>
|
||||||
{
|
{
|
||||||
// Suppress specific notifications
|
messageFilters.AddOutgoingFilter(next => async (context, cancellationToken) =>
|
||||||
if (context.JsonRpcMessage is JsonRpcNotification notification &&
|
|
||||||
notification.Method == "notifications/progress")
|
|
||||||
{
|
{
|
||||||
return; // Don't send this notification
|
// Suppress specific notifications
|
||||||
}
|
if (context.JsonRpcMessage is JsonRpcNotification notification &&
|
||||||
|
notification.Method == "notifications/progress")
|
||||||
|
{
|
||||||
|
return; // Don't send this notification
|
||||||
|
}
|
||||||
|
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -150,26 +163,29 @@ You can suppress outgoing messages by not calling `next`:
|
||||||
Outgoing message filters can send additional messages by calling `next` with a new `MessageContext`:
|
Outgoing message filters can send additional messages by calling `next` with a new `MessageContext`:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
.AddOutgoingMessageFilter(next => async (context, cancellationToken) =>
|
.WithMessageFilters(messageFilters =>
|
||||||
{
|
{
|
||||||
// Send an extra notification before certain responses
|
messageFilters.AddOutgoingFilter(next => async (context, cancellationToken) =>
|
||||||
if (context.JsonRpcMessage is JsonRpcResponse response &&
|
|
||||||
response.Result is JsonObject result &&
|
|
||||||
result.ContainsKey("tools"))
|
|
||||||
{
|
{
|
||||||
var notification = new JsonRpcNotification
|
// Send an extra notification before certain responses
|
||||||
|
if (context.JsonRpcMessage is JsonRpcResponse response &&
|
||||||
|
response.Result is JsonObject result &&
|
||||||
|
result.ContainsKey("tools"))
|
||||||
{
|
{
|
||||||
Method = "custom/toolsListed",
|
var notification = new JsonRpcNotification
|
||||||
Params = new JsonObject { ["timestamp"] = DateTime.UtcNow.ToString("O") },
|
|
||||||
Context = new JsonRpcMessageContext
|
|
||||||
{
|
{
|
||||||
RelatedTransport = context.JsonRpcMessage.Context?.RelatedTransport
|
Method = "custom/toolsListed",
|
||||||
}
|
Params = new JsonObject { ["timestamp"] = DateTime.UtcNow.ToString("O") },
|
||||||
};
|
Context = new JsonRpcMessageContext
|
||||||
await next(new MessageContext(context.Server, notification), cancellationToken);
|
{
|
||||||
}
|
RelatedTransport = context.JsonRpcMessage.Context?.RelatedTransport
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await next(new MessageContext(context.Server, notification), cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -179,11 +195,17 @@ Message filters execute in registration order, with the first registered filter
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
services.AddMcpServer()
|
services.AddMcpServer()
|
||||||
.AddIncomingMessageFilter(incomingFilter1) // Incoming: executes first (outermost)
|
.WithMessageFilters(messageFilters =>
|
||||||
.AddIncomingMessageFilter(incomingFilter2) // Incoming: executes second
|
{
|
||||||
.AddOutgoingMessageFilter(outgoingFilter1) // Outgoing: executes first (outermost)
|
messageFilters.AddIncomingFilter(incomingFilter1); // Incoming: executes first (outermost)
|
||||||
.AddOutgoingMessageFilter(outgoingFilter2) // Outgoing: executes second
|
messageFilters.AddIncomingFilter(incomingFilter2); // Incoming: executes second
|
||||||
.AddListToolsFilter(toolsFilter) // Request-specific filter
|
messageFilters.AddOutgoingFilter(outgoingFilter1); // Outgoing: executes first (outermost)
|
||||||
|
messageFilters.AddOutgoingFilter(outgoingFilter2); // Outgoing: executes second
|
||||||
|
})
|
||||||
|
.WithRequestFilters(requestFilters =>
|
||||||
|
{
|
||||||
|
requestFilters.AddListToolsFilter(toolsFilter); // Request-specific filter
|
||||||
|
})
|
||||||
.WithTools<MyTools>();
|
.WithTools<MyTools>();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -218,21 +240,25 @@ OutgoingFilter1 (after next)
|
||||||
The `Items` dictionary allows you to pass data between filters processing the same message:
|
The `Items` dictionary allows you to pass data between filters processing the same message:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
.AddIncomingMessageFilter(next => async (context, cancellationToken) =>
|
.WithMessageFilters(messageFilters =>
|
||||||
{
|
{
|
||||||
context.Items["requestStartTime"] = DateTime.UtcNow;
|
messageFilters.AddIncomingFilter(next => async (context, cancellationToken) =>
|
||||||
await next(context, cancellationToken);
|
|
||||||
})
|
|
||||||
.AddIncomingMessageFilter(next => async (context, cancellationToken) =>
|
|
||||||
{
|
|
||||||
await next(context, cancellationToken);
|
|
||||||
|
|
||||||
if (context.Items.TryGetValue("requestStartTime", out var startTime))
|
|
||||||
{
|
{
|
||||||
var elapsed = DateTime.UtcNow - (DateTime)startTime;
|
context.Items["requestStartTime"] = DateTime.UtcNow;
|
||||||
var logger = context.Services?.GetService<ILogger<Program>>();
|
await next(context, cancellationToken);
|
||||||
logger?.LogInformation($"Request processed in {elapsed.TotalMilliseconds}ms");
|
});
|
||||||
}
|
|
||||||
|
messageFilters.AddIncomingFilter(next => async (context, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await next(context, cancellationToken);
|
||||||
|
|
||||||
|
if (context.Items.TryGetValue("requestStartTime", out var startTime))
|
||||||
|
{
|
||||||
|
var elapsed = DateTime.UtcNow - (DateTime)startTime;
|
||||||
|
var logger = context.Services?.GetService<ILogger<Program>>();
|
||||||
|
logger?.LogInformation($"Request processed in {elapsed.TotalMilliseconds}ms");
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -247,18 +273,21 @@ services.AddMcpServer()
|
||||||
// Your base handler logic
|
// Your base handler logic
|
||||||
return new ListToolsResult { Tools = GetTools() };
|
return new ListToolsResult { Tools = GetTools() };
|
||||||
})
|
})
|
||||||
.AddListToolsFilter(next => async (context, cancellationToken) =>
|
.WithRequestFilters(requestFilters =>
|
||||||
{
|
{
|
||||||
var logger = context.Services?.GetService<ILogger<Program>>();
|
requestFilters.AddListToolsFilter(next => async (context, cancellationToken) =>
|
||||||
|
{
|
||||||
|
var logger = context.Services?.GetService<ILogger<Program>>();
|
||||||
|
|
||||||
// Pre-processing logic
|
// Pre-processing logic
|
||||||
logger?.LogInformation("Before handler execution");
|
logger?.LogInformation("Before handler execution");
|
||||||
|
|
||||||
var result = await next(context, cancellationToken);
|
var result = await next(context, cancellationToken);
|
||||||
|
|
||||||
// Post-processing logic
|
// Post-processing logic
|
||||||
logger?.LogInformation("After handler execution");
|
logger?.LogInformation("After handler execution");
|
||||||
return result;
|
return result;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -267,9 +296,12 @@ services.AddMcpServer()
|
||||||
```csharp
|
```csharp
|
||||||
services.AddMcpServer()
|
services.AddMcpServer()
|
||||||
.WithListToolsHandler(baseHandler)
|
.WithListToolsHandler(baseHandler)
|
||||||
.AddListToolsFilter(filter1) // Executes first (outermost)
|
.WithRequestFilters(requestFilters =>
|
||||||
.AddListToolsFilter(filter2) // Executes second
|
{
|
||||||
.AddListToolsFilter(filter3); // Executes third (closest to handler)
|
requestFilters.AddListToolsFilter(filter1); // Executes first (outermost)
|
||||||
|
requestFilters.AddListToolsFilter(filter2); // Executes second
|
||||||
|
requestFilters.AddListToolsFilter(filter3); // Executes third (closest to handler)
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Execution flow: `filter1 -> filter2 -> filter3 -> baseHandler -> filter3 -> filter2 -> filter1`
|
Execution flow: `filter1 -> filter2 -> filter3 -> baseHandler -> filter3 -> filter2 -> filter1`
|
||||||
|
|
@ -279,76 +311,91 @@ Execution flow: `filter1 -> filter2 -> filter3 -> baseHandler -> filter3 -> filt
|
||||||
### Logging
|
### Logging
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
.AddListToolsFilter(next => async (context, cancellationToken) =>
|
.WithRequestFilters(requestFilters =>
|
||||||
{
|
{
|
||||||
var logger = context.Services?.GetService<ILogger<Program>>();
|
requestFilters.AddListToolsFilter(next => async (context, cancellationToken) =>
|
||||||
|
{
|
||||||
|
var logger = context.Services?.GetService<ILogger<Program>>();
|
||||||
|
|
||||||
logger?.LogInformation($"Processing request from {context.Meta.ProgressToken}");
|
logger?.LogInformation($"Processing request from {context.Meta.ProgressToken}");
|
||||||
var result = await next(context, cancellationToken);
|
var result = await next(context, cancellationToken);
|
||||||
logger?.LogInformation($"Returning {result.Tools?.Count ?? 0} tools");
|
logger?.LogInformation($"Returning {result.Tools?.Count ?? 0} tools");
|
||||||
return result;
|
return result;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Error Handling
|
### Error Handling
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
.AddCallToolFilter(next => async (context, cancellationToken) =>
|
.WithRequestFilters(requestFilters =>
|
||||||
{
|
{
|
||||||
try
|
requestFilters.AddCallToolFilter(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
return await next(context, cancellationToken);
|
try
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return new CallToolResult
|
|
||||||
{
|
{
|
||||||
Content = new[] { new TextContent { Type = "text", Text = $"Error: {ex.Message}" } },
|
return await next(context, cancellationToken);
|
||||||
IsError = true
|
}
|
||||||
};
|
catch (Exception ex)
|
||||||
}
|
{
|
||||||
|
var logger = context.Services?.GetService<ILogger<Program>>();
|
||||||
|
logger?.LogError(ex, "Error while processing CallTool request for {ProgressToken}", context.Meta.ProgressToken);
|
||||||
|
|
||||||
|
return new CallToolResult
|
||||||
|
{
|
||||||
|
Content = new[] { new TextContent { Type = "text", Text = "An unexpected error occurred while processing the tool call." } },
|
||||||
|
IsError = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Performance Monitoring
|
### Performance Monitoring
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
.AddListToolsFilter(next => async (context, cancellationToken) =>
|
.WithRequestFilters(requestFilters =>
|
||||||
{
|
{
|
||||||
var logger = context.Services?.GetService<ILogger<Program>>();
|
requestFilters.AddListToolsFilter(next => async (context, cancellationToken) =>
|
||||||
|
{
|
||||||
|
var logger = context.Services?.GetService<ILogger<Program>>();
|
||||||
|
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
var result = await next(context, cancellationToken);
|
var result = await next(context, cancellationToken);
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
logger?.LogInformation($"Handler took {stopwatch.ElapsedMilliseconds}ms");
|
logger?.LogInformation($"Handler took {stopwatch.ElapsedMilliseconds}ms");
|
||||||
return result;
|
return result;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Caching
|
### Caching
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
.AddListResourcesFilter(next => async (context, cancellationToken) =>
|
.WithRequestFilters(requestFilters =>
|
||||||
{
|
{
|
||||||
var cache = context.Services!.GetRequiredService<IMemoryCache>();
|
requestFilters.AddListResourcesFilter(next => async (context, cancellationToken) =>
|
||||||
|
|
||||||
var cacheKey = $"resources:{context.Params.Cursor}";
|
|
||||||
if (cache.TryGetValue(cacheKey, out var cached))
|
|
||||||
{
|
{
|
||||||
return (ListResourcesResult)cached;
|
var cache = context.Services!.GetRequiredService<IMemoryCache>();
|
||||||
}
|
|
||||||
|
|
||||||
var result = await next(context, cancellationToken);
|
var cacheKey = $"resources:{context.Params.Cursor}";
|
||||||
cache.Set(cacheKey, result, TimeSpan.FromMinutes(5));
|
if (cache.TryGetValue(cacheKey, out var cached))
|
||||||
return result;
|
{
|
||||||
|
return (ListResourcesResult)cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await next(context, cancellationToken);
|
||||||
|
cache.Set(cacheKey, result, TimeSpan.FromMinutes(5));
|
||||||
|
return result;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Built-in Authorization Filters
|
## Built-in Authorization Request Filters
|
||||||
|
|
||||||
When using the ASP.NET Core integration (`ModelContextProtocol.AspNetCore`), you can add authorization filters to support `[Authorize]` and `[AllowAnonymous]` attributes on MCP server tools, prompts, and resources by calling `AddAuthorizationFilters()` on your MCP server builder.
|
When using the ASP.NET Core integration (`ModelContextProtocol.AspNetCore`), you can add authorization filters to support `[Authorize]` and `[AllowAnonymous]` attributes on MCP server tools, prompts, and resources by calling `AddAuthorizationFilters()` on your MCP server builder.
|
||||||
|
|
||||||
### Enabling Authorization Filters
|
### Enabling Authorization Request Filters
|
||||||
|
|
||||||
To enable authorization support, call `AddAuthorizationFilters()` when configuring your MCP server:
|
To enable authorization support, call `AddAuthorizationFilters()` when configuring your MCP server:
|
||||||
|
|
||||||
|
|
@ -455,25 +502,31 @@ This allows you to implement logging, metrics, or other cross-cutting concerns t
|
||||||
```csharp
|
```csharp
|
||||||
services.AddMcpServer()
|
services.AddMcpServer()
|
||||||
.WithHttpTransport()
|
.WithHttpTransport()
|
||||||
.AddListToolsFilter(next => async (context, cancellationToken) =>
|
.WithRequestFilters(requestFilters =>
|
||||||
{
|
{
|
||||||
var logger = context.Services?.GetService<ILogger<Program>>();
|
requestFilters.AddListToolsFilter(next => async (context, cancellationToken) =>
|
||||||
|
{
|
||||||
|
var logger = context.Services?.GetService<ILogger<Program>>();
|
||||||
|
|
||||||
// This filter runs BEFORE authorization - sees all tools
|
// This filter runs BEFORE authorization - sees all tools
|
||||||
logger?.LogInformation("Request for tools list - will see all tools");
|
logger?.LogInformation("Request for tools list - will see all tools");
|
||||||
var result = await next(context, cancellationToken);
|
var result = await next(context, cancellationToken);
|
||||||
logger?.LogInformation($"Returning {result.Tools?.Count ?? 0} tools after authorization");
|
logger?.LogInformation($"Returning {result.Tools?.Count ?? 0} tools after authorization");
|
||||||
return result;
|
return result;
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.AddAuthorizationFilters() // Authorization filtering happens here
|
.AddAuthorizationFilters() // Authorization filtering happens here
|
||||||
.AddListToolsFilter(next => async (context, cancellationToken) =>
|
.WithRequestFilters(requestFilters =>
|
||||||
{
|
{
|
||||||
var logger = context.Services?.GetService<ILogger<Program>>();
|
requestFilters.AddListToolsFilter(next => async (context, cancellationToken) =>
|
||||||
|
{
|
||||||
|
var logger = context.Services?.GetService<ILogger<Program>>();
|
||||||
|
|
||||||
// This filter runs AFTER authorization - only sees authorized tools
|
// This filter runs AFTER authorization - only sees authorized tools
|
||||||
var result = await next(context, cancellationToken);
|
var result = await next(context, cancellationToken);
|
||||||
logger?.LogInformation($"Post-auth filter sees {result.Tools?.Count ?? 0} authorized tools");
|
logger?.LogInformation($"Post-auth filter sees {result.Tools?.Count ?? 0} authorized tools");
|
||||||
return result;
|
return result;
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.WithTools<WeatherTools>();
|
.WithTools<WeatherTools>();
|
||||||
```
|
```
|
||||||
|
|
@ -494,10 +547,13 @@ builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport()
|
.WithHttpTransport()
|
||||||
.AddAuthorizationFilters() // Required for authorization support
|
.AddAuthorizationFilters() // Required for authorization support
|
||||||
.WithTools<WeatherTools>()
|
.WithTools<WeatherTools>()
|
||||||
.AddCallToolFilter(next => async (context, cancellationToken) =>
|
.WithRequestFilters(requestFilters =>
|
||||||
{
|
{
|
||||||
// Custom call tool logic
|
requestFilters.AddCallToolFilter(next => async (context, cancellationToken) =>
|
||||||
return await next(context, cancellationToken);
|
{
|
||||||
|
// Custom call tool logic
|
||||||
|
return await next(context, cancellationToken);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
@ -511,19 +567,22 @@ app.Run();
|
||||||
You can also create custom authorization filters using the filter methods:
|
You can also create custom authorization filters using the filter methods:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
.AddCallToolFilter(next => async (context, cancellationToken) =>
|
.WithRequestFilters(requestFilters =>
|
||||||
{
|
{
|
||||||
// Custom authorization logic
|
requestFilters.AddCallToolFilter(next => async (context, cancellationToken) =>
|
||||||
if (context.User?.Identity?.IsAuthenticated != true)
|
|
||||||
{
|
{
|
||||||
return new CallToolResult
|
// Custom authorization logic
|
||||||
|
if (context.User?.Identity?.IsAuthenticated != true)
|
||||||
{
|
{
|
||||||
Content = [new TextContent { Text = "Custom: Authentication required" }],
|
return new CallToolResult
|
||||||
IsError = true
|
{
|
||||||
};
|
Content = [new TextContent { Text = "Custom: Authentication required" }],
|
||||||
}
|
IsError = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return await next(context, cancellationToken);
|
return await next(context, cancellationToken);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,4 +22,9 @@ internal static class Obsoletions
|
||||||
public const string LegacyTitledEnumSchema_DiagnosticId = "MCP9001";
|
public const string LegacyTitledEnumSchema_DiagnosticId = "MCP9001";
|
||||||
public const string LegacyTitledEnumSchema_Message = "The EnumSchema and LegacyTitledEnumSchema APIs are deprecated as of specification version 2025-11-25 and will be removed in a future major version. See SEP-1330 for more information.";
|
public const string LegacyTitledEnumSchema_Message = "The EnumSchema and LegacyTitledEnumSchema APIs are deprecated as of specification version 2025-11-25 and will be removed in a future major version. See SEP-1330 for more information.";
|
||||||
public const string LegacyTitledEnumSchema_Url = "https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330";
|
public const string LegacyTitledEnumSchema_Url = "https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330";
|
||||||
|
|
||||||
|
public const string MessageAndRequestFilter_DiagnosticId = "MCP9002";
|
||||||
|
public const string MessageAndRequestFilter_Url = "https://github.com/modelcontextprotocol/csharp-sdk/pull/1308";
|
||||||
|
public const string MessageFilter_Message = "Use WithMessageFilters() instead.";
|
||||||
|
public const string RequestFilter_Message = "Use WithRequestFilters() instead.";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private void ConfigureListToolsFilter(McpServerOptions options)
|
private void ConfigureListToolsFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.ListToolsFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.ListToolsFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
context.Items[AuthorizationFilterInvokedKey] = true;
|
context.Items[AuthorizationFilterInvokedKey] = true;
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private static void CheckListToolsFilter(McpServerOptions options)
|
private static void CheckListToolsFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.ListToolsFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.ListToolsFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var result = await next(context, cancellationToken);
|
var result = await next(context, cancellationToken);
|
||||||
|
|
||||||
|
|
@ -73,7 +73,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private void ConfigureCallToolFilter(McpServerOptions options)
|
private void ConfigureCallToolFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.CallToolFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.CallToolFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var authResult = await GetAuthorizationResultAsync(context.User, context.MatchedPrimitive, context.Services, context);
|
var authResult = await GetAuthorizationResultAsync(context.User, context.MatchedPrimitive, context.Services, context);
|
||||||
if (!authResult.Succeeded)
|
if (!authResult.Succeeded)
|
||||||
|
|
@ -89,7 +89,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private static void CheckCallToolFilter(McpServerOptions options)
|
private static void CheckCallToolFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.CallToolFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.CallToolFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
if (HasAuthorizationMetadata(context.MatchedPrimitive)
|
if (HasAuthorizationMetadata(context.MatchedPrimitive)
|
||||||
&& !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
|
&& !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
|
||||||
|
|
@ -103,7 +103,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private void ConfigureListResourcesFilter(McpServerOptions options)
|
private void ConfigureListResourcesFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
context.Items[AuthorizationFilterInvokedKey] = true;
|
context.Items[AuthorizationFilterInvokedKey] = true;
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private static void CheckListResourcesFilter(McpServerOptions options)
|
private static void CheckListResourcesFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.ListResourcesFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var result = await next(context, cancellationToken);
|
var result = await next(context, cancellationToken);
|
||||||
|
|
||||||
|
|
@ -133,7 +133,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private void ConfigureListResourceTemplatesFilter(McpServerOptions options)
|
private void ConfigureListResourceTemplatesFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
context.Items[AuthorizationFilterInvokedKey] = true;
|
context.Items[AuthorizationFilterInvokedKey] = true;
|
||||||
|
|
||||||
|
|
@ -147,7 +147,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private static void CheckListResourceTemplatesFilter(McpServerOptions options)
|
private static void CheckListResourceTemplatesFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.ListResourceTemplatesFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var result = await next(context, cancellationToken);
|
var result = await next(context, cancellationToken);
|
||||||
|
|
||||||
|
|
@ -163,7 +163,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private void ConfigureReadResourceFilter(McpServerOptions options)
|
private void ConfigureReadResourceFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
context.Items[AuthorizationFilterInvokedKey] = true;
|
context.Items[AuthorizationFilterInvokedKey] = true;
|
||||||
|
|
||||||
|
|
@ -179,7 +179,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private static void CheckReadResourceFilter(McpServerOptions options)
|
private static void CheckReadResourceFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.ReadResourceFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
if (HasAuthorizationMetadata(context.MatchedPrimitive)
|
if (HasAuthorizationMetadata(context.MatchedPrimitive)
|
||||||
&& !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
|
&& !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
|
||||||
|
|
@ -193,7 +193,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private void ConfigureListPromptsFilter(McpServerOptions options)
|
private void ConfigureListPromptsFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
context.Items[AuthorizationFilterInvokedKey] = true;
|
context.Items[AuthorizationFilterInvokedKey] = true;
|
||||||
|
|
||||||
|
|
@ -207,7 +207,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private static void CheckListPromptsFilter(McpServerOptions options)
|
private static void CheckListPromptsFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.ListPromptsFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var result = await next(context, cancellationToken);
|
var result = await next(context, cancellationToken);
|
||||||
|
|
||||||
|
|
@ -223,7 +223,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private void ConfigureGetPromptFilter(McpServerOptions options)
|
private void ConfigureGetPromptFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.GetPromptFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.GetPromptFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
context.Items[AuthorizationFilterInvokedKey] = true;
|
context.Items[AuthorizationFilterInvokedKey] = true;
|
||||||
|
|
||||||
|
|
@ -239,7 +239,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
|
||||||
|
|
||||||
private static void CheckGetPromptFilter(McpServerOptions options)
|
private static void CheckGetPromptFilter(McpServerOptions options)
|
||||||
{
|
{
|
||||||
options.Filters.GetPromptFilters.Add(next => async (context, cancellationToken) =>
|
options.Filters.Request.GetPromptFilters.Add(next => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
if (HasAuthorizationMetadata(context.MatchedPrimitive)
|
if (HasAuthorizationMetadata(context.MatchedPrimitive)
|
||||||
&& !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
|
&& !context.Items.ContainsKey(AuthorizationFilterInvokedKey))
|
||||||
|
|
|
||||||
42
src/ModelContextProtocol.Core/Server/McpMessageFilters.cs
Normal file
42
src/ModelContextProtocol.Core/Server/McpMessageFilters.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
using ModelContextProtocol.Protocol;
|
||||||
|
|
||||||
|
namespace ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides grouped message filter collections.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class McpMessageFilters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for all incoming JSON-RPC messages.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// These filters intercept all incoming JSON-RPC messages before they are processed by the server,
|
||||||
|
/// including requests, notifications, responses, and errors. The filters can perform logging,
|
||||||
|
/// authentication, rate limiting, or other cross-cutting concerns that apply to all message types.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Message filters are applied before request-specific filters. If a message filter does not call
|
||||||
|
/// the next handler in the pipeline, the default handlers will not be executed.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpMessageFilter> IncomingFilters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for all outgoing JSON-RPC messages.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// These filters intercept all outgoing JSON-RPC messages before they are sent to the client,
|
||||||
|
/// including responses, notifications, and errors. The filters can perform logging,
|
||||||
|
/// redaction, auditing, or other cross-cutting concerns that apply to all message types.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// If a message filter does not call the next handler in the pipeline, the message will not be sent.
|
||||||
|
/// Filters may also call the next handler multiple times with different messages to emit additional
|
||||||
|
/// server-to-client messages.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpMessageFilter> OutgoingFilters { get; } = [];
|
||||||
|
}
|
||||||
157
src/ModelContextProtocol.Core/Server/McpRequestFilters.cs
Normal file
157
src/ModelContextProtocol.Core/Server/McpRequestFilters.cs
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
using ModelContextProtocol.Protocol;
|
||||||
|
|
||||||
|
namespace ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides grouped request-specific filter collections.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class McpRequestFilters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for the list-tools handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// These filters wrap handlers that return a list of available tools when requested by a client.
|
||||||
|
/// The filters can modify, log, or perform additional operations on requests and responses for
|
||||||
|
/// <see cref="RequestMethods.ToolsList"/> requests. It supports pagination through the cursor mechanism,
|
||||||
|
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more tools.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// These filters work alongside any tools defined in the <see cref="McpServerTool"/> collection.
|
||||||
|
/// Tools from both sources will be combined when returning results to clients.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpRequestFilter<ListToolsRequestParams, ListToolsResult>> ListToolsFilters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for the call-tool handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// These filters wrap handlers that are invoked when a client makes a call to a tool that isn't found in the <see cref="McpServerTool"/> collection.
|
||||||
|
/// The filters can modify, log, or perform additional operations on requests and responses for
|
||||||
|
/// <see cref="RequestMethods.ToolsCall"/> requests. The handler should implement logic to execute the requested tool and return appropriate results.
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpRequestFilter<CallToolRequestParams, CallToolResult>> CallToolFilters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for the list-prompts handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// These filters wrap handlers that return a list of available prompts when requested by a client.
|
||||||
|
/// The filters can modify, log, or perform additional operations on requests and responses for
|
||||||
|
/// <see cref="RequestMethods.PromptsList"/> requests. It supports pagination through the cursor mechanism,
|
||||||
|
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more prompts.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// These filters work alongside any prompts defined in the <see cref="McpServerPrompt"/> collection.
|
||||||
|
/// Prompts from both sources will be combined when returning results to clients.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpRequestFilter<ListPromptsRequestParams, ListPromptsResult>> ListPromptsFilters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for the get-prompt handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// These filters wrap handlers that are invoked when a client requests details for a specific prompt that isn't found in the <see cref="McpServerPrompt"/> collection.
|
||||||
|
/// The filters can modify, log, or perform additional operations on requests and responses for
|
||||||
|
/// <see cref="RequestMethods.PromptsGet"/> requests. The handler should implement logic to fetch or generate the requested prompt and return appropriate results.
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpRequestFilter<GetPromptRequestParams, GetPromptResult>> GetPromptFilters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for the list-resource-templates handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// These filters wrap handlers that return a list of available resource templates when requested by a client.
|
||||||
|
/// The filters can modify, log, or perform additional operations on requests and responses for
|
||||||
|
/// <see cref="RequestMethods.ResourcesTemplatesList"/> requests. It supports pagination through the cursor mechanism,
|
||||||
|
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more resource templates.
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpRequestFilter<ListResourceTemplatesRequestParams, ListResourceTemplatesResult>> ListResourceTemplatesFilters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for the list-resources handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// These filters wrap handlers that return a list of available resources when requested by a client.
|
||||||
|
/// The filters can modify, log, or perform additional operations on requests and responses for
|
||||||
|
/// <see cref="RequestMethods.ResourcesList"/> requests. It supports pagination through the cursor mechanism,
|
||||||
|
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more resources.
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpRequestFilter<ListResourcesRequestParams, ListResourcesResult>> ListResourcesFilters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for the read-resource handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// These filters wrap handlers that are invoked when a client requests the content of a specific resource identified by its URI.
|
||||||
|
/// The filters can modify, log, or perform additional operations on requests and responses for
|
||||||
|
/// <see cref="RequestMethods.ResourcesRead"/> requests. The handler should implement logic to locate and retrieve the requested resource.
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpRequestFilter<ReadResourceRequestParams, ReadResourceResult>> ReadResourceFilters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for the complete-handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// These filters wrap handlers that provide auto-completion suggestions for prompt arguments or resource references in the Model Context Protocol.
|
||||||
|
/// The filters can modify, log, or perform additional operations on requests and responses for
|
||||||
|
/// <see cref="RequestMethods.CompletionComplete"/> requests. The handler processes auto-completion requests, returning a list of suggestions based on the
|
||||||
|
/// reference type and current argument value.
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpRequestFilter<CompleteRequestParams, CompleteResult>> CompleteFilters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for the subscribe-to-resources handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// These filters wrap handlers that are invoked when a client wants to receive notifications about changes to specific resources or resource patterns.
|
||||||
|
/// The filters can modify, log, or perform additional operations on requests and responses for
|
||||||
|
/// <see cref="RequestMethods.ResourcesSubscribe"/> requests. The handler should implement logic to register the client's interest in the specified resources
|
||||||
|
/// and set up the necessary infrastructure to send notifications when those resources change.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// After a successful subscription, the server should send resource change notifications to the client
|
||||||
|
/// whenever a relevant resource is created, updated, or deleted.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpRequestFilter<SubscribeRequestParams, EmptyResult>> SubscribeToResourcesFilters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for the unsubscribe-from-resources handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// These filters wrap handlers that are invoked when a client wants to stop receiving notifications about previously subscribed resources.
|
||||||
|
/// The filters can modify, log, or perform additional operations on requests and responses for
|
||||||
|
/// <see cref="RequestMethods.ResourcesUnsubscribe"/> requests. The handler should implement logic to remove the client's subscriptions to the specified resources
|
||||||
|
/// and clean up any associated resources.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// After a successful unsubscription, the server should no longer send resource change notifications
|
||||||
|
/// to the client for the specified resources.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpRequestFilter<UnsubscribeRequestParams, EmptyResult>> UnsubscribeFromResourcesFilters { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the filters for the set-logging-level handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// These filters wrap handlers that process <see cref="RequestMethods.LoggingSetLevel"/> requests from clients. When set, it enables
|
||||||
|
/// clients to control which log messages they receive by specifying a minimum severity threshold.
|
||||||
|
/// The filters can modify, log, or perform additional operations on requests and responses for
|
||||||
|
/// <see cref="RequestMethods.LoggingSetLevel"/> requests.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// After handling a level change request, the server typically begins sending log messages
|
||||||
|
/// at or above the specified level to the client as notifications/message notifications.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public IList<McpRequestFilter<SetLevelRequestParams, EmptyResult>> SetLoggingLevelFilters { get; } = [];
|
||||||
|
}
|
||||||
|
|
@ -12,183 +12,12 @@ namespace ModelContextProtocol.Server;
|
||||||
public sealed class McpServerFilters
|
public sealed class McpServerFilters
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the filters for all incoming JSON-RPC messages.
|
/// Gets the filters for incoming and outgoing JSON-RPC messages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
public McpMessageFilters Message { get; } = new();
|
||||||
/// <para>
|
|
||||||
/// These filters intercept all incoming JSON-RPC messages before they are processed by the server,
|
|
||||||
/// including requests, notifications, responses, and errors. The filters can perform logging,
|
|
||||||
/// authentication, rate limiting, or other cross-cutting concerns that apply to all message types.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// Message filters are applied before request-specific filters. If a message filter does not call
|
|
||||||
/// the next handler in the pipeline, the default handlers will not be executed.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpMessageFilter> IncomingMessageFilters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the filters for all outgoing JSON-RPC messages.
|
/// Gets the filters for request-specific MCP handler pipelines.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
public McpRequestFilters Request { get; } = new();
|
||||||
/// <para>
|
|
||||||
/// These filters intercept all outgoing JSON-RPC messages before they are sent to the client,
|
|
||||||
/// including responses, notifications, and errors. The filters can perform logging,
|
|
||||||
/// redaction, auditing, or other cross-cutting concerns that apply to all message types.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// If a message filter does not call the next handler in the pipeline, the message will not be sent.
|
|
||||||
/// Filters may also call the next handler multiple times with different messages to emit additional
|
|
||||||
/// server-to-client messages.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpMessageFilter> OutgoingMessageFilters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the filters for the list-tools handler pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>
|
|
||||||
/// These filters wrap handlers that return a list of available tools when requested by a client.
|
|
||||||
/// The filters can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ToolsList"/> requests. It supports pagination through the cursor mechanism,
|
|
||||||
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more tools.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// These filters work alongside any tools defined in the <see cref="McpServerTool"/> collection.
|
|
||||||
/// Tools from both sources will be combined when returning results to clients.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpRequestFilter<ListToolsRequestParams, ListToolsResult>> ListToolsFilters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the filters for the call-tool handler pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// These filters wrap handlers that are invoked when a client makes a call to a tool that isn't found in the <see cref="McpServerTool"/> collection.
|
|
||||||
/// The filters can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ToolsCall"/> requests. The handler should implement logic to execute the requested tool and return appropriate results.
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpRequestFilter<CallToolRequestParams, CallToolResult>> CallToolFilters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the filters for the list-prompts handler pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>
|
|
||||||
/// These filters wrap handlers that return a list of available prompts when requested by a client.
|
|
||||||
/// The filters can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.PromptsList"/> requests. It supports pagination through the cursor mechanism,
|
|
||||||
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more prompts.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// These filters work alongside any prompts defined in the <see cref="McpServerPrompt"/> collection.
|
|
||||||
/// Prompts from both sources will be combined when returning results to clients.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpRequestFilter<ListPromptsRequestParams, ListPromptsResult>> ListPromptsFilters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the filters for the get-prompt handler pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// These filters wrap handlers that are invoked when a client requests details for a specific prompt that isn't found in the <see cref="McpServerPrompt"/> collection.
|
|
||||||
/// The filters can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.PromptsGet"/> requests. The handler should implement logic to fetch or generate the requested prompt and return appropriate results.
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpRequestFilter<GetPromptRequestParams, GetPromptResult>> GetPromptFilters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the filters for the list-resource-templates handler pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// These filters wrap handlers that return a list of available resource templates when requested by a client.
|
|
||||||
/// The filters can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ResourcesTemplatesList"/> requests. It supports pagination through the cursor mechanism,
|
|
||||||
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more resource templates.
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpRequestFilter<ListResourceTemplatesRequestParams, ListResourceTemplatesResult>> ListResourceTemplatesFilters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the filters for the list-resources handler pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// These filters wrap handlers that return a list of available resources when requested by a client.
|
|
||||||
/// The filters can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ResourcesList"/> requests. It supports pagination through the cursor mechanism,
|
|
||||||
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more resources.
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpRequestFilter<ListResourcesRequestParams, ListResourcesResult>> ListResourcesFilters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the filters for the read-resource handler pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// These filters wrap handlers that are invoked when a client requests the content of a specific resource identified by its URI.
|
|
||||||
/// The filters can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ResourcesRead"/> requests. The handler should implement logic to locate and retrieve the requested resource.
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpRequestFilter<ReadResourceRequestParams, ReadResourceResult>> ReadResourceFilters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the filters for the complete-handler pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// These filters wrap handlers that provide auto-completion suggestions for prompt arguments or resource references in the Model Context Protocol.
|
|
||||||
/// The filters can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.CompletionComplete"/> requests. The handler processes auto-completion requests, returning a list of suggestions based on the
|
|
||||||
/// reference type and current argument value.
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpRequestFilter<CompleteRequestParams, CompleteResult>> CompleteFilters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the filters for the subscribe-to-resources handler pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>
|
|
||||||
/// These filters wrap handlers that are invoked when a client wants to receive notifications about changes to specific resources or resource patterns.
|
|
||||||
/// The filters can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ResourcesSubscribe"/> requests. The handler should implement logic to register the client's interest in the specified resources
|
|
||||||
/// and set up the necessary infrastructure to send notifications when those resources change.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// After a successful subscription, the server should send resource change notifications to the client
|
|
||||||
/// whenever a relevant resource is created, updated, or deleted.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpRequestFilter<SubscribeRequestParams, EmptyResult>> SubscribeToResourcesFilters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the filters for the unsubscribe-from-resources handler pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>
|
|
||||||
/// These filters wrap handlers that are invoked when a client wants to stop receiving notifications about previously subscribed resources.
|
|
||||||
/// The filters can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ResourcesUnsubscribe"/> requests. The handler should implement logic to remove the client's subscriptions to the specified resources
|
|
||||||
/// and clean up any associated resources.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// After a successful unsubscription, the server should no longer send resource change notifications
|
|
||||||
/// to the client for the specified resources.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpRequestFilter<UnsubscribeRequestParams, EmptyResult>> UnsubscribeFromResourcesFilters { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the filters for the set-logging-level handler pipeline.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>
|
|
||||||
/// These filters wrap handlers that process <see cref="RequestMethods.LoggingSetLevel"/> requests from clients. When set, it enables
|
|
||||||
/// clients to control which log messages they receive by specifying a minimum severity threshold.
|
|
||||||
/// The filters can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.LoggingSetLevel"/> requests.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// After handling a level change request, the server typically begins sending log messages
|
|
||||||
/// at or above the specified level to the client as notifications/message notifications.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public List<McpRequestFilter<SetLevelRequestParams, EmptyResult>> SetLoggingLevelFilters { get; } = [];
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,8 +119,8 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
}
|
}
|
||||||
|
|
||||||
// And initialize the session.
|
// And initialize the session.
|
||||||
var incomingMessageFilter = BuildMessageFilterPipeline(options.Filters.IncomingMessageFilters);
|
var incomingMessageFilter = BuildMessageFilterPipeline(options.Filters.Message.IncomingFilters);
|
||||||
var outgoingMessageFilter = BuildMessageFilterPipeline(options.Filters.OutgoingMessageFilters);
|
var outgoingMessageFilter = BuildMessageFilterPipeline(options.Filters.Message.OutgoingFilters);
|
||||||
_sessionHandler = new McpSessionHandler(
|
_sessionHandler = new McpSessionHandler(
|
||||||
isServer: true,
|
isServer: true,
|
||||||
_sessionTransport,
|
_sessionTransport,
|
||||||
|
|
@ -260,7 +260,7 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
}
|
}
|
||||||
|
|
||||||
completeHandler ??= (static async (_, __) => new CompleteResult());
|
completeHandler ??= (static async (_, __) => new CompleteResult());
|
||||||
completeHandler = BuildFilterPipeline(completeHandler, options.Filters.CompleteFilters);
|
completeHandler = BuildFilterPipeline(completeHandler, options.Filters.Request.CompleteFilters);
|
||||||
|
|
||||||
ServerCapabilities.Completions = new();
|
ServerCapabilities.Completions = new();
|
||||||
|
|
||||||
|
|
@ -366,9 +366,9 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
// subscribe = true;
|
// subscribe = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
listResourcesHandler = BuildFilterPipeline(listResourcesHandler, options.Filters.ListResourcesFilters);
|
listResourcesHandler = BuildFilterPipeline(listResourcesHandler, options.Filters.Request.ListResourcesFilters);
|
||||||
listResourceTemplatesHandler = BuildFilterPipeline(listResourceTemplatesHandler, options.Filters.ListResourceTemplatesFilters);
|
listResourceTemplatesHandler = BuildFilterPipeline(listResourceTemplatesHandler, options.Filters.Request.ListResourceTemplatesFilters);
|
||||||
readResourceHandler = BuildFilterPipeline(readResourceHandler, options.Filters.ReadResourceFilters, handler =>
|
readResourceHandler = BuildFilterPipeline(readResourceHandler, options.Filters.Request.ReadResourceFilters, handler =>
|
||||||
async (request, cancellationToken) =>
|
async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Initial handler that sets MatchedPrimitive
|
// Initial handler that sets MatchedPrimitive
|
||||||
|
|
@ -405,8 +405,8 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
subscribeHandler = BuildFilterPipeline(subscribeHandler, options.Filters.SubscribeToResourcesFilters);
|
subscribeHandler = BuildFilterPipeline(subscribeHandler, options.Filters.Request.SubscribeToResourcesFilters);
|
||||||
unsubscribeHandler = BuildFilterPipeline(unsubscribeHandler, options.Filters.UnsubscribeFromResourcesFilters);
|
unsubscribeHandler = BuildFilterPipeline(unsubscribeHandler, options.Filters.Request.UnsubscribeFromResourcesFilters);
|
||||||
|
|
||||||
ServerCapabilities.Resources.ListChanged = listChanged;
|
ServerCapabilities.Resources.ListChanged = listChanged;
|
||||||
ServerCapabilities.Resources.Subscribe = subscribe;
|
ServerCapabilities.Resources.Subscribe = subscribe;
|
||||||
|
|
@ -496,8 +496,8 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
listChanged = true;
|
listChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
listPromptsHandler = BuildFilterPipeline(listPromptsHandler, options.Filters.ListPromptsFilters);
|
listPromptsHandler = BuildFilterPipeline(listPromptsHandler, options.Filters.Request.ListPromptsFilters);
|
||||||
getPromptHandler = BuildFilterPipeline(getPromptHandler, options.Filters.GetPromptFilters, handler =>
|
getPromptHandler = BuildFilterPipeline(getPromptHandler, options.Filters.Request.GetPromptFilters, handler =>
|
||||||
async (request, cancellationToken) =>
|
async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Initial handler that sets MatchedPrimitive
|
// Initial handler that sets MatchedPrimitive
|
||||||
|
|
@ -618,8 +618,8 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
listChanged = true;
|
listChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
listToolsHandler = BuildFilterPipeline(listToolsHandler, options.Filters.ListToolsFilters);
|
listToolsHandler = BuildFilterPipeline(listToolsHandler, options.Filters.Request.ListToolsFilters);
|
||||||
callToolHandler = BuildFilterPipeline(callToolHandler, options.Filters.CallToolFilters, handler =>
|
callToolHandler = BuildFilterPipeline(callToolHandler, options.Filters.Request.CallToolFilters, handler =>
|
||||||
async (request, cancellationToken) =>
|
async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Initial handler that sets MatchedPrimitive
|
// Initial handler that sets MatchedPrimitive
|
||||||
|
|
@ -819,7 +819,7 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
// Apply filters to the handler
|
// Apply filters to the handler
|
||||||
if (setLoggingLevelHandler is not null)
|
if (setLoggingLevelHandler is not null)
|
||||||
{
|
{
|
||||||
setLoggingLevelHandler = BuildFilterPipeline(setLoggingLevelHandler, options.Filters.SetLoggingLevelFilters);
|
setLoggingLevelHandler = BuildFilterPipeline(setLoggingLevelHandler, options.Filters.Request.SetLoggingLevelFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerCapabilities.Logging = new();
|
ServerCapabilities.Logging = new();
|
||||||
|
|
@ -903,7 +903,7 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
|
|
||||||
private static McpRequestHandler<TParams, TResult> BuildFilterPipeline<TParams, TResult>(
|
private static McpRequestHandler<TParams, TResult> BuildFilterPipeline<TParams, TResult>(
|
||||||
McpRequestHandler<TParams, TResult> baseHandler,
|
McpRequestHandler<TParams, TResult> baseHandler,
|
||||||
List<McpRequestFilter<TParams, TResult>> filters,
|
IList<McpRequestFilter<TParams, TResult>> filters,
|
||||||
McpRequestFilter<TParams, TResult>? initialHandler = null)
|
McpRequestFilter<TParams, TResult>? initialHandler = null)
|
||||||
{
|
{
|
||||||
var current = baseHandler;
|
var current = baseHandler;
|
||||||
|
|
@ -921,7 +921,7 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JsonRpcMessageFilter BuildMessageFilterPipeline(List<McpMessageFilter> filters)
|
private JsonRpcMessageFilter BuildMessageFilterPipeline(IList<McpMessageFilter> filters)
|
||||||
{
|
{
|
||||||
if (filters.Count == 0)
|
if (filters.Count == 0)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -94,29 +94,21 @@ public sealed class McpServerOptions
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public ClientCapabilities? KnownClientCapabilities { get; set; }
|
public ClientCapabilities? KnownClientCapabilities { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the container of handlers used by the server for processing protocol messages.
|
||||||
|
/// </summary>
|
||||||
|
public McpServerHandlers Handlers { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the filter collections for MCP server handlers.
|
/// Gets the filter collections for MCP server handlers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This property provides access to filter collections that can be used to modify the behavior
|
/// This property provides access to filter collections that can be used to modify the behavior
|
||||||
/// of various MCP server handlers. Filters are applied in reverse order, so the last filter
|
/// of various MCP server handlers. The first filter added is the outermost (first to execute),
|
||||||
/// added will be the outermost (first to execute).
|
/// and each subsequent filter wraps closer to the handler.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public McpServerFilters Filters { get; } = new();
|
public McpServerFilters Filters { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the container of handlers used by the server for processing protocol messages.
|
|
||||||
/// </summary>
|
|
||||||
public McpServerHandlers Handlers
|
|
||||||
{
|
|
||||||
get => field ??= new();
|
|
||||||
set
|
|
||||||
{
|
|
||||||
Throw.IfNull(value);
|
|
||||||
field = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a collection of tools served by the server.
|
/// Gets or sets a collection of tools served by the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ namespace ModelContextProtocol.Server;
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// This type is typically received as a parameter in message filter delegates registered via
|
/// This type is typically received as a parameter in message filter delegates registered via
|
||||||
/// <see cref="McpServerFilters.IncomingMessageFilters"/> or <see cref="McpServerFilters.OutgoingMessageFilters"/>.
|
/// <see cref="McpServerFilters.Message"/>'s <see cref="McpMessageFilters.IncomingFilters"/> or
|
||||||
|
/// <see cref="McpMessageFilters.OutgoingFilters"/> collections.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class MessageContext
|
public class MessageContext
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
internal sealed class DefaultMcpMessageFilterBuilder(IMcpServerBuilder serverBuilder) : IMcpMessageFilterBuilder
|
||||||
|
{
|
||||||
|
public IServiceCollection Services { get; } = serverBuilder.Services;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
internal sealed class DefaultMcpRequestFilterBuilder(IMcpServerBuilder serverBuilder) : IMcpRequestFilterBuilder
|
||||||
|
{
|
||||||
|
public IServiceCollection Services { get; } = serverBuilder.Services;
|
||||||
|
}
|
||||||
14
src/ModelContextProtocol/IMcpMessageFilterBuilder.cs
Normal file
14
src/ModelContextProtocol/IMcpMessageFilterBuilder.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a builder for configuring message-level MCP server filters.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpMessageFilterBuilder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the associated service collection.
|
||||||
|
/// </summary>
|
||||||
|
IServiceCollection Services { get; }
|
||||||
|
}
|
||||||
14
src/ModelContextProtocol/IMcpRequestFilterBuilder.cs
Normal file
14
src/ModelContextProtocol/IMcpRequestFilterBuilder.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a builder for configuring request-specific MCP server filters.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpRequestFilterBuilder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the associated service collection.
|
||||||
|
/// </summary>
|
||||||
|
IServiceCollection Services { get; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
using ModelContextProtocol;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for configuring message-level MCP server filters.
|
||||||
|
/// </summary>
|
||||||
|
public static class McpMessageFilterBuilderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to intercept all incoming JSON-RPC messages.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The message filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the message handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpMessageFilterBuilder AddIncomingFilter(this IMcpMessageFilterBuilder builder, McpMessageFilter filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Message.IncomingFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to intercept all outgoing JSON-RPC messages.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The message filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the message handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpMessageFilterBuilder AddOutgoingFilter(this IMcpMessageFilterBuilder builder, McpMessageFilter filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Message.OutgoingFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
176
src/ModelContextProtocol/McpRequestFilterBuilderExtensions.cs
Normal file
176
src/ModelContextProtocol/McpRequestFilterBuilderExtensions.cs
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
using ModelContextProtocol;
|
||||||
|
using ModelContextProtocol.Protocol;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for configuring request-specific MCP server filters.
|
||||||
|
/// </summary>
|
||||||
|
public static class McpRequestFilterBuilderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to the list resource templates handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The request filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpRequestFilterBuilder AddListResourceTemplatesFilter(this IMcpRequestFilterBuilder builder, McpRequestFilter<ListResourceTemplatesRequestParams, ListResourceTemplatesResult> filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Request.ListResourceTemplatesFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to the list tools handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The request filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpRequestFilterBuilder AddListToolsFilter(this IMcpRequestFilterBuilder builder, McpRequestFilter<ListToolsRequestParams, ListToolsResult> filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Request.ListToolsFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to the call tool handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The request filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpRequestFilterBuilder AddCallToolFilter(this IMcpRequestFilterBuilder builder, McpRequestFilter<CallToolRequestParams, CallToolResult> filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Request.CallToolFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to the list prompts handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The request filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpRequestFilterBuilder AddListPromptsFilter(this IMcpRequestFilterBuilder builder, McpRequestFilter<ListPromptsRequestParams, ListPromptsResult> filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Request.ListPromptsFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to the get prompt handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The request filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpRequestFilterBuilder AddGetPromptFilter(this IMcpRequestFilterBuilder builder, McpRequestFilter<GetPromptRequestParams, GetPromptResult> filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Request.GetPromptFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to the list resources handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The request filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpRequestFilterBuilder AddListResourcesFilter(this IMcpRequestFilterBuilder builder, McpRequestFilter<ListResourcesRequestParams, ListResourcesResult> filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Request.ListResourcesFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to the read resource handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The request filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpRequestFilterBuilder AddReadResourceFilter(this IMcpRequestFilterBuilder builder, McpRequestFilter<ReadResourceRequestParams, ReadResourceResult> filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Request.ReadResourceFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to the complete handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The request filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpRequestFilterBuilder AddCompleteFilter(this IMcpRequestFilterBuilder builder, McpRequestFilter<CompleteRequestParams, CompleteResult> filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Request.CompleteFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to the subscribe-to-resources handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The request filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpRequestFilterBuilder AddSubscribeToResourcesFilter(this IMcpRequestFilterBuilder builder, McpRequestFilter<SubscribeRequestParams, EmptyResult> filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Request.SubscribeToResourcesFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to the unsubscribe-from-resources handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The request filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpRequestFilterBuilder AddUnsubscribeFromResourcesFilter(this IMcpRequestFilterBuilder builder, McpRequestFilter<UnsubscribeRequestParams, EmptyResult> filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Request.UnsubscribeFromResourcesFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a filter to the set logging level handler pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The request filter builder instance.</param>
|
||||||
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
public static IMcpRequestFilterBuilder AddSetLoggingLevelFilter(this IMcpRequestFilterBuilder builder, McpRequestFilter<SetLevelRequestParams, EmptyResult> filter)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(filter);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerOptions>(options => options.Filters.Request.SetLoggingLevelFilters.Add(filter));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -893,28 +893,49 @@ public static partial class McpServerBuilderExtensions
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Filters
|
#region Filters
|
||||||
|
/// <summary>
|
||||||
|
/// Configures message-level filters for the MCP server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder instance.</param>
|
||||||
|
/// <param name="configure">A callback used to register message filters.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="builder"/> or <paramref name="configure"/> is <see langword="null"/>.</exception>
|
||||||
|
public static IMcpServerBuilder WithMessageFilters(this IMcpServerBuilder builder, Action<IMcpMessageFilterBuilder> configure)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(configure);
|
||||||
|
|
||||||
|
configure(new DefaultMcpMessageFilterBuilder(builder));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures request-specific filters for the MCP server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder instance.</param>
|
||||||
|
/// <param name="configure">A callback used to register request-specific filters.</param>
|
||||||
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="builder"/> or <paramref name="configure"/> is <see langword="null"/>.</exception>
|
||||||
|
public static IMcpServerBuilder WithRequestFilters(this IMcpServerBuilder builder, Action<IMcpRequestFilterBuilder> configure)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(configure);
|
||||||
|
|
||||||
|
configure(new DefaultMcpRequestFilterBuilder(builder));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CS0436 // ObsoleteAttribute polyfill conflicts with framework type on netstandard.
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to the list resource templates handler pipeline.
|
/// Adds a filter to the list resource templates handler pipeline.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the handler.</param>
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.RequestFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddListResourceTemplatesFilter(this IMcpServerBuilder builder, McpRequestFilter<ListResourceTemplatesRequestParams, ListResourceTemplatesResult> filter) =>
|
||||||
/// <para>
|
builder.WithRequestFilters(filters => filters.AddListResourceTemplatesFilter(filter));
|
||||||
/// This filter wraps handlers that return a list of available resource templates when requested by a client.
|
|
||||||
/// The filter can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ResourcesTemplatesList"/> requests. It supports pagination through the cursor mechanism,
|
|
||||||
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more resource templates.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddListResourceTemplatesFilter(this IMcpServerBuilder builder, McpRequestFilter<ListResourceTemplatesRequestParams, ListResourceTemplatesResult> filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.ListResourceTemplatesFilters.Add(filter));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to the list tools handler pipeline.
|
/// Adds a filter to the list tools handler pipeline.
|
||||||
|
|
@ -922,26 +943,9 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the handler.</param>
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.RequestFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddListToolsFilter(this IMcpServerBuilder builder, McpRequestFilter<ListToolsRequestParams, ListToolsResult> filter) =>
|
||||||
/// <para>
|
builder.WithRequestFilters(filters => filters.AddListToolsFilter(filter));
|
||||||
/// This filter wraps handlers that return a list of available tools when requested by a client.
|
|
||||||
/// The filter can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ToolsList"/> requests. It supports pagination through the cursor mechanism,
|
|
||||||
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more tools.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// This filter works alongside any tools defined in the <see cref="McpServerTool"/> collection.
|
|
||||||
/// Tools from both sources will be combined when returning results to clients.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddListToolsFilter(this IMcpServerBuilder builder, McpRequestFilter<ListToolsRequestParams, ListToolsResult> filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.ListToolsFilters.Add(filter));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to the call tool handler pipeline.
|
/// Adds a filter to the call tool handler pipeline.
|
||||||
|
|
@ -949,21 +953,9 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the handler.</param>
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.RequestFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddCallToolFilter(this IMcpServerBuilder builder, McpRequestFilter<CallToolRequestParams, CallToolResult> filter) =>
|
||||||
/// <para>
|
builder.WithRequestFilters(filters => filters.AddCallToolFilter(filter));
|
||||||
/// This filter wraps handlers that are invoked when a client makes a call to a tool that isn't found in the <see cref="McpServerTool"/> collection.
|
|
||||||
/// The filter can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ToolsCall"/> requests. The handler should implement logic to execute the requested tool and return appropriate results.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddCallToolFilter(this IMcpServerBuilder builder, McpRequestFilter<CallToolRequestParams, CallToolResult> filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.CallToolFilters.Add(filter));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to the list prompts handler pipeline.
|
/// Adds a filter to the list prompts handler pipeline.
|
||||||
|
|
@ -971,26 +963,9 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the handler.</param>
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.RequestFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddListPromptsFilter(this IMcpServerBuilder builder, McpRequestFilter<ListPromptsRequestParams, ListPromptsResult> filter) =>
|
||||||
/// <para>
|
builder.WithRequestFilters(filters => filters.AddListPromptsFilter(filter));
|
||||||
/// This filter wraps handlers that return a list of available prompts when requested by a client.
|
|
||||||
/// The filter can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.PromptsList"/> requests. It supports pagination through the cursor mechanism,
|
|
||||||
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more prompts.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// This filter works alongside any prompts defined in the <see cref="McpServerPrompt"/> collection.
|
|
||||||
/// Prompts from both sources will be combined when returning results to clients.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddListPromptsFilter(this IMcpServerBuilder builder, McpRequestFilter<ListPromptsRequestParams, ListPromptsResult> filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.ListPromptsFilters.Add(filter));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to the get prompt handler pipeline.
|
/// Adds a filter to the get prompt handler pipeline.
|
||||||
|
|
@ -998,21 +973,9 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the handler.</param>
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.RequestFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddGetPromptFilter(this IMcpServerBuilder builder, McpRequestFilter<GetPromptRequestParams, GetPromptResult> filter) =>
|
||||||
/// <para>
|
builder.WithRequestFilters(filters => filters.AddGetPromptFilter(filter));
|
||||||
/// This filter wraps handlers that are invoked when a client requests details for a specific prompt that isn't found in the <see cref="McpServerPrompt"/> collection.
|
|
||||||
/// The filter can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.PromptsGet"/> requests. The handler should implement logic to fetch or generate the requested prompt and return appropriate results.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddGetPromptFilter(this IMcpServerBuilder builder, McpRequestFilter<GetPromptRequestParams, GetPromptResult> filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.GetPromptFilters.Add(filter));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to the list resources handler pipeline.
|
/// Adds a filter to the list resources handler pipeline.
|
||||||
|
|
@ -1020,22 +983,9 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the handler.</param>
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.RequestFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddListResourcesFilter(this IMcpServerBuilder builder, McpRequestFilter<ListResourcesRequestParams, ListResourcesResult> filter) =>
|
||||||
/// <para>
|
builder.WithRequestFilters(filters => filters.AddListResourcesFilter(filter));
|
||||||
/// This filter wraps handlers that return a list of available resources when requested by a client.
|
|
||||||
/// The filter can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ResourcesList"/> requests. It supports pagination through the cursor mechanism,
|
|
||||||
/// where the client can make repeated calls with the cursor returned by the previous call to retrieve more resources.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddListResourcesFilter(this IMcpServerBuilder builder, McpRequestFilter<ListResourcesRequestParams, ListResourcesResult> filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.ListResourcesFilters.Add(filter));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to the read resource handler pipeline.
|
/// Adds a filter to the read resource handler pipeline.
|
||||||
|
|
@ -1043,21 +993,9 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the handler.</param>
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.RequestFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddReadResourceFilter(this IMcpServerBuilder builder, McpRequestFilter<ReadResourceRequestParams, ReadResourceResult> filter) =>
|
||||||
/// <para>
|
builder.WithRequestFilters(filters => filters.AddReadResourceFilter(filter));
|
||||||
/// This filter wraps handlers that are invoked when a client requests the content of a specific resource identified by its URI.
|
|
||||||
/// The filter can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ResourcesRead"/> requests. The handler should implement logic to locate and retrieve the requested resource.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddReadResourceFilter(this IMcpServerBuilder builder, McpRequestFilter<ReadResourceRequestParams, ReadResourceResult> filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.ReadResourceFilters.Add(filter));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to the complete handler pipeline.
|
/// Adds a filter to the complete handler pipeline.
|
||||||
|
|
@ -1065,22 +1003,9 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the handler.</param>
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.RequestFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddCompleteFilter(this IMcpServerBuilder builder, McpRequestFilter<CompleteRequestParams, CompleteResult> filter) =>
|
||||||
/// <para>
|
builder.WithRequestFilters(filters => filters.AddCompleteFilter(filter));
|
||||||
/// This filter wraps handlers that provide auto-completion suggestions for prompt arguments or resource references in the Model Context Protocol.
|
|
||||||
/// The filter can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.CompletionComplete"/> requests. The handler processes auto-completion requests, returning a list of suggestions based on the
|
|
||||||
/// reference type and current argument value.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddCompleteFilter(this IMcpServerBuilder builder, McpRequestFilter<CompleteRequestParams, CompleteResult> filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.CompleteFilters.Add(filter));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to the subscribe-to-resources handler pipeline.
|
/// Adds a filter to the subscribe-to-resources handler pipeline.
|
||||||
|
|
@ -1088,26 +1013,9 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the handler.</param>
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.RequestFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddSubscribeToResourcesFilter(this IMcpServerBuilder builder, McpRequestFilter<SubscribeRequestParams, EmptyResult> filter) =>
|
||||||
/// <para>
|
builder.WithRequestFilters(filters => filters.AddSubscribeToResourcesFilter(filter));
|
||||||
/// This filter wraps handlers that are invoked when a client wants to receive notifications about changes to specific resources or resource patterns.
|
|
||||||
/// The filter can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ResourcesSubscribe"/> requests. The handler should implement logic to register the client's interest in the specified resources
|
|
||||||
/// and set up the necessary infrastructure to send notifications when those resources change.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// After a successful subscription, the server should send resource change notifications to the client
|
|
||||||
/// whenever a relevant resource is created, updated, or deleted.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddSubscribeToResourcesFilter(this IMcpServerBuilder builder, McpRequestFilter<SubscribeRequestParams, EmptyResult> filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.SubscribeToResourcesFilters.Add(filter));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to the unsubscribe-from-resources handler pipeline.
|
/// Adds a filter to the unsubscribe-from-resources handler pipeline.
|
||||||
|
|
@ -1115,26 +1023,9 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the handler.</param>
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.RequestFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddUnsubscribeFromResourcesFilter(this IMcpServerBuilder builder, McpRequestFilter<UnsubscribeRequestParams, EmptyResult> filter) =>
|
||||||
/// <para>
|
builder.WithRequestFilters(filters => filters.AddUnsubscribeFromResourcesFilter(filter));
|
||||||
/// This filter wraps handlers that are invoked when a client wants to stop receiving notifications about previously subscribed resources.
|
|
||||||
/// The filter can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.ResourcesUnsubscribe"/> requests. The handler should implement logic to remove the client's subscriptions to the specified resources
|
|
||||||
/// and clean up any associated resources.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// After a successful unsubscription, the server should no longer send resource change notifications
|
|
||||||
/// to the client for the specified resources.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddUnsubscribeFromResourcesFilter(this IMcpServerBuilder builder, McpRequestFilter<UnsubscribeRequestParams, EmptyResult> filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.UnsubscribeFromResourcesFilters.Add(filter));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to the set logging level handler pipeline.
|
/// Adds a filter to the set logging level handler pipeline.
|
||||||
|
|
@ -1142,26 +1033,9 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the handler.</param>
|
/// <param name="filter">The filter function that wraps the handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.RequestFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddSetLoggingLevelFilter(this IMcpServerBuilder builder, McpRequestFilter<SetLevelRequestParams, EmptyResult> filter) =>
|
||||||
/// <para>
|
builder.WithRequestFilters(filters => filters.AddSetLoggingLevelFilter(filter));
|
||||||
/// This filter wraps handlers that process <see cref="RequestMethods.LoggingSetLevel"/> requests from clients. When set, it enables
|
|
||||||
/// clients to control which log messages they receive by specifying a minimum severity threshold.
|
|
||||||
/// The filter can modify, log, or perform additional operations on requests and responses for
|
|
||||||
/// <see cref="RequestMethods.LoggingSetLevel"/> requests.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// After handling a level change request, the server typically begins sending log messages
|
|
||||||
/// at or above the specified level to the client as notifications or message notifications.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddSetLoggingLevelFilter(this IMcpServerBuilder builder, McpRequestFilter<SetLevelRequestParams, EmptyResult> filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.SetLoggingLevelFilters.Add(filter));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to intercept all incoming JSON-RPC messages.
|
/// Adds a filter to intercept all incoming JSON-RPC messages.
|
||||||
|
|
@ -1169,36 +1043,9 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the message handler.</param>
|
/// <param name="filter">The filter function that wraps the message handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.MessageFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddIncomingMessageFilter(this IMcpServerBuilder builder, McpMessageFilter filter) =>
|
||||||
/// <para>
|
builder.WithMessageFilters(filters => filters.AddIncomingFilter(filter));
|
||||||
/// This filter intercepts all incoming JSON-RPC messages before they are processed by the server,
|
|
||||||
/// including requests, notifications, responses, and errors. The filter can perform logging,
|
|
||||||
/// authentication, rate limiting, or other cross-cutting concerns that apply to all message types.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// Message filters are applied before request-specific filters. If a message filter does not call
|
|
||||||
/// the next handler in the pipeline, the default handlers will not be executed.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// Filters are applied in the order they are registered, with the first registered filter being the outermost.
|
|
||||||
/// Each filter receives the next handler in the pipeline and can choose to:
|
|
||||||
/// <list type="bullet">
|
|
||||||
/// <item><description>Call the next handler to continue processing: <c>await next(context, cancellationToken)</c></description></item>
|
|
||||||
/// <item><description>Skip the default handlers entirely by not calling next</description></item>
|
|
||||||
/// <item><description>Perform operations before and/or after calling next</description></item>
|
|
||||||
/// <item><description>Catch and handle exceptions from inner handlers</description></item>
|
|
||||||
/// </list>
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddIncomingMessageFilter(this IMcpServerBuilder builder, McpMessageFilter filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
Throw.IfNull(filter);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.IncomingMessageFilters.Add(filter));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a filter to intercept all outgoing JSON-RPC messages.
|
/// Adds a filter to intercept all outgoing JSON-RPC messages.
|
||||||
|
|
@ -1206,26 +1053,11 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="filter">The filter function that wraps the message handler.</param>
|
/// <param name="filter">The filter function that wraps the message handler.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
[Obsolete(Obsoletions.MessageFilter_Message, DiagnosticId = Obsoletions.MessageAndRequestFilter_DiagnosticId, UrlFormat = Obsoletions.MessageAndRequestFilter_Url)]
|
||||||
/// <remarks>
|
public static IMcpServerBuilder AddOutgoingMessageFilter(this IMcpServerBuilder builder, McpMessageFilter filter) =>
|
||||||
/// <para>
|
builder.WithMessageFilters(filters => filters.AddOutgoingFilter(filter));
|
||||||
/// This filter intercepts all outgoing JSON-RPC messages before they are sent to the client,
|
|
||||||
/// including responses, notifications, and errors. The filter can perform logging, redaction,
|
|
||||||
/// auditing, or other cross-cutting concerns that apply to all message types.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// If a message filter does not call the next handler in the pipeline, the message will not be sent.
|
|
||||||
/// Filters may also call the next handler multiple times with different messages to emit additional
|
|
||||||
/// server-to-client messages.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder AddOutgoingMessageFilter(this IMcpServerBuilder builder, McpMessageFilter filter)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerOptions>(options => options.Filters.OutgoingMessageFilters.Add(filter));
|
#pragma warning restore CS0436
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Transports
|
#region Transports
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\Common\Throw.cs" Link="Throw.cs" />
|
<Compile Include="..\Common\Throw.cs" Link="Throw.cs" />
|
||||||
|
<Compile Include="..\Common\Obsoletions.cs" Link="Obsoletions.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ public class Program
|
||||||
})
|
})
|
||||||
.WithTools<ConformanceTools>()
|
.WithTools<ConformanceTools>()
|
||||||
.WithTools([ConformanceTools.CreateJsonSchema202012Tool()])
|
.WithTools([ConformanceTools.CreateJsonSchema202012Tool()])
|
||||||
.AddCallToolFilter(next => async (request, cancellationToken) =>
|
.WithRequestFilters(filters => filters.AddCallToolFilter(next => async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var result = await next(request, cancellationToken);
|
var result = await next(request, cancellationToken);
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ public class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
})
|
}))
|
||||||
.WithPrompts<ConformancePrompts>()
|
.WithPrompts<ConformancePrompts>()
|
||||||
.WithResources<ConformanceResources>()
|
.WithResources<ConformanceResources>()
|
||||||
.WithSubscribeToResourcesHandler(async (ctx, ct) =>
|
.WithSubscribeToResourcesHandler(async (ctx, ct) =>
|
||||||
|
|
|
||||||
|
|
@ -90,307 +90,313 @@ public class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
const int pageSize = 10;
|
const int pageSize = 10;
|
||||||
options.Handlers = new()
|
options.Handlers.ListToolsHandler = async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
ListToolsHandler = async (request, cancellationToken) =>
|
return new ListToolsResult
|
||||||
{
|
{
|
||||||
return new ListToolsResult
|
Tools =
|
||||||
{
|
[
|
||||||
Tools =
|
new Tool
|
||||||
[
|
{
|
||||||
new Tool
|
Name = "echo",
|
||||||
{
|
Description = "Echoes the input back to the client.",
|
||||||
Name = "echo",
|
InputSchema = JsonElement.Parse("""
|
||||||
Description = "Echoes the input back to the client.",
|
|
||||||
InputSchema = JsonElement.Parse("""
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The input to echo back."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["message"]
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
},
|
|
||||||
new Tool
|
|
||||||
{
|
|
||||||
Name = "echoSessionId",
|
|
||||||
Description = "Echoes the session id back to the client.",
|
|
||||||
InputSchema = JsonElement.Parse("""
|
|
||||||
{
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
},
|
|
||||||
new Tool
|
|
||||||
{
|
|
||||||
Name = "sampleLLM",
|
|
||||||
Description = "Samples from an LLM using MCP's sampling feature.",
|
|
||||||
InputSchema = JsonElement.Parse("""
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"prompt": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The prompt to send to the LLM"
|
|
||||||
},
|
|
||||||
"maxTokens": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Maximum number of tokens to generate"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["prompt", "maxTokens"]
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
},
|
|
||||||
new Tool
|
|
||||||
{
|
|
||||||
Name = "longRunning",
|
|
||||||
Description = "Simulates a long-running operation that supports task-based execution.",
|
|
||||||
InputSchema = JsonElement.Parse("""
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"durationMs": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Duration of the operation in milliseconds"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["durationMs"]
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Execution = new ToolExecution
|
|
||||||
{
|
{
|
||||||
TaskSupport = ToolTaskSupport.Optional
|
"type": "object",
|
||||||
}
|
"properties": {
|
||||||
}
|
"message": {
|
||||||
]
|
"type": "string",
|
||||||
};
|
"description": "The input to echo back."
|
||||||
},
|
|
||||||
CallToolHandler = async (request, cancellationToken) =>
|
|
||||||
{
|
|
||||||
if (request.Params is null)
|
|
||||||
{
|
|
||||||
throw new McpProtocolException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
|
||||||
}
|
|
||||||
if (request.Params.Name == "echo")
|
|
||||||
{
|
|
||||||
if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
|
||||||
{
|
|
||||||
throw new McpProtocolException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
|
||||||
}
|
|
||||||
return new CallToolResult
|
|
||||||
{
|
|
||||||
Content = [new TextContentBlock { Text = $"Echo: {message}" }]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (request.Params.Name == "echoSessionId")
|
|
||||||
{
|
|
||||||
return new CallToolResult
|
|
||||||
{
|
|
||||||
Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (request.Params.Name == "sampleLLM")
|
|
||||||
{
|
|
||||||
if (request.Params.Arguments is null ||
|
|
||||||
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
|
|
||||||
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
|
||||||
{
|
|
||||||
throw new McpProtocolException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
|
|
||||||
}
|
|
||||||
var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())),
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
|
|
||||||
return new CallToolResult
|
|
||||||
{
|
|
||||||
Content = [new TextContentBlock { Text = $"LLM sampling result: {sampleResult.Content.OfType<TextContentBlock>().FirstOrDefault()?.Text}" }]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (request.Params.Name == "longRunning")
|
|
||||||
{
|
|
||||||
if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("durationMs", out var durationMsValue))
|
|
||||||
{
|
|
||||||
throw new McpProtocolException("Missing required argument 'durationMs'", McpErrorCode.InvalidParams);
|
|
||||||
}
|
|
||||||
int durationMs = Convert.ToInt32(durationMsValue.ToString());
|
|
||||||
await Task.Delay(durationMs, cancellationToken);
|
|
||||||
return new CallToolResult
|
|
||||||
{
|
|
||||||
Content = [new TextContentBlock { Text = $"Long-running operation completed after {durationMs}ms" }]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new McpProtocolException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ListResourceTemplatesHandler = async (request, cancellationToken) =>
|
|
||||||
{
|
|
||||||
|
|
||||||
return new ListResourceTemplatesResult
|
|
||||||
{
|
|
||||||
ResourceTemplates = [
|
|
||||||
new ResourceTemplate
|
|
||||||
{
|
|
||||||
UriTemplate = "test://dynamic/resource/{id}",
|
|
||||||
Name = "Dynamic Resource",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
ListResourcesHandler = async (request, cancellationToken) =>
|
|
||||||
{
|
|
||||||
int startIndex = 0;
|
|
||||||
var requestParams = request.Params ?? new();
|
|
||||||
if (requestParams.Cursor is not null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(requestParams.Cursor));
|
|
||||||
startIndex = Convert.ToInt32(startIndexAsString);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new McpProtocolException($"Invalid cursor: '{requestParams.Cursor}'", e, McpErrorCode.InvalidParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int endIndex = Math.Min(startIndex + pageSize, resources.Count);
|
|
||||||
string? nextCursor = null;
|
|
||||||
|
|
||||||
if (endIndex < resources.Count)
|
|
||||||
{
|
|
||||||
nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ListResourcesResult
|
|
||||||
{
|
|
||||||
NextCursor = nextCursor,
|
|
||||||
Resources = resources.GetRange(startIndex, endIndex - startIndex)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
ReadResourceHandler = async (request, cancellationToken) =>
|
|
||||||
{
|
|
||||||
if (request.Params?.Uri is null)
|
|
||||||
{
|
|
||||||
throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
|
||||||
{
|
|
||||||
var id = request.Params.Uri.Split('/').LastOrDefault();
|
|
||||||
if (string.IsNullOrEmpty(id))
|
|
||||||
{
|
|
||||||
throw new McpProtocolException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ReadResourceResult
|
|
||||||
{
|
|
||||||
Contents = [
|
|
||||||
new TextResourceContents
|
|
||||||
{
|
|
||||||
Uri = request.Params.Uri,
|
|
||||||
MimeType = "text/plain",
|
|
||||||
Text = $"Dynamic resource {id}: This is a plaintext resource"
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
};
|
"required": ["message"]
|
||||||
}
|
}
|
||||||
|
"""),
|
||||||
|
},
|
||||||
|
new Tool
|
||||||
|
{
|
||||||
|
Name = "echoSessionId",
|
||||||
|
Description = "Echoes the session id back to the client.",
|
||||||
|
InputSchema = JsonElement.Parse("""
|
||||||
|
{
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
},
|
||||||
|
new Tool
|
||||||
|
{
|
||||||
|
Name = "sampleLLM",
|
||||||
|
Description = "Samples from an LLM using MCP's sampling feature.",
|
||||||
|
InputSchema = JsonElement.Parse("""
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"prompt": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The prompt to send to the LLM"
|
||||||
|
},
|
||||||
|
"maxTokens": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Maximum number of tokens to generate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["prompt", "maxTokens"]
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
},
|
||||||
|
new Tool
|
||||||
|
{
|
||||||
|
Name = "longRunning",
|
||||||
|
Description = "Simulates a long-running operation that supports task-based execution.",
|
||||||
|
InputSchema = JsonElement.Parse("""
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"durationMs": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Duration of the operation in milliseconds"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["durationMs"]
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
Execution = new ToolExecution
|
||||||
|
{
|
||||||
|
TaskSupport = ToolTaskSupport.Optional
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ??
|
options.Handlers.CallToolHandler = async (request, cancellationToken) =>
|
||||||
throw new McpProtocolException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.ResourceNotFound);
|
{
|
||||||
|
if (request.Params is null)
|
||||||
|
{
|
||||||
|
throw new McpProtocolException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Params.Name == "echo")
|
||||||
|
{
|
||||||
|
if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
||||||
|
{
|
||||||
|
throw new McpProtocolException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||||
|
}
|
||||||
|
return new CallToolResult
|
||||||
|
{
|
||||||
|
Content = [new TextContentBlock { Text = $"Echo: {message}" }]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (request.Params.Name == "echoSessionId")
|
||||||
|
{
|
||||||
|
return new CallToolResult
|
||||||
|
{
|
||||||
|
Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (request.Params.Name == "sampleLLM")
|
||||||
|
{
|
||||||
|
if (request.Params.Arguments is null ||
|
||||||
|
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
|
||||||
|
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
||||||
|
{
|
||||||
|
throw new McpProtocolException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
|
||||||
|
}
|
||||||
|
var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())),
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
return new CallToolResult
|
||||||
|
{
|
||||||
|
Content = [new TextContentBlock { Text = $"LLM sampling result: {sampleResult.Content.OfType<TextContentBlock>().FirstOrDefault()?.Text}" }]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (request.Params.Name == "longRunning")
|
||||||
|
{
|
||||||
|
if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("durationMs", out var durationMsValue))
|
||||||
|
{
|
||||||
|
throw new McpProtocolException("Missing required argument 'durationMs'", McpErrorCode.InvalidParams);
|
||||||
|
}
|
||||||
|
int durationMs = Convert.ToInt32(durationMsValue.ToString());
|
||||||
|
await Task.Delay(durationMs, cancellationToken);
|
||||||
|
return new CallToolResult
|
||||||
|
{
|
||||||
|
Content = [new TextContentBlock { Text = $"Long-running operation completed after {durationMs}ms" }]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new McpProtocolException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
options.Handlers.ListResourceTemplatesHandler = async (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
return new ListResourceTemplatesResult
|
||||||
|
{
|
||||||
|
ResourceTemplates = [
|
||||||
|
new ResourceTemplate
|
||||||
|
{
|
||||||
|
UriTemplate = "test://dynamic/resource/{id}",
|
||||||
|
Name = "Dynamic Resource",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
options.Handlers.ListResourcesHandler = async (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
int startIndex = 0;
|
||||||
|
var requestParams = request.Params ?? new();
|
||||||
|
|
||||||
|
if (requestParams.Cursor is not null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(requestParams.Cursor));
|
||||||
|
startIndex = Convert.ToInt32(startIndexAsString);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new McpProtocolException($"Invalid cursor: '{requestParams.Cursor}'", e, McpErrorCode.InvalidParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int endIndex = Math.Min(startIndex + pageSize, resources.Count);
|
||||||
|
string? nextCursor = null;
|
||||||
|
|
||||||
|
if (endIndex < resources.Count)
|
||||||
|
{
|
||||||
|
nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ListResourcesResult
|
||||||
|
{
|
||||||
|
NextCursor = nextCursor,
|
||||||
|
Resources = resources.GetRange(startIndex, endIndex - startIndex)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
options.Handlers.ReadResourceHandler = async (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
if (request.Params?.Uri is null)
|
||||||
|
{
|
||||||
|
throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||||
|
{
|
||||||
|
var id = request.Params.Uri.Split('/').LastOrDefault();
|
||||||
|
if (string.IsNullOrEmpty(id))
|
||||||
|
{
|
||||||
|
throw new McpProtocolException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||||
|
}
|
||||||
|
|
||||||
return new ReadResourceResult
|
return new ReadResourceResult
|
||||||
{
|
{
|
||||||
Contents = [contents]
|
Contents = [
|
||||||
};
|
new TextResourceContents
|
||||||
},
|
|
||||||
ListPromptsHandler = async (request, cancellationToken) =>
|
|
||||||
{
|
|
||||||
return new ListPromptsResult
|
|
||||||
{
|
|
||||||
Prompts = [
|
|
||||||
new Prompt
|
|
||||||
{
|
|
||||||
Name = "simple_prompt",
|
|
||||||
Description = "A prompt without arguments"
|
|
||||||
},
|
|
||||||
new Prompt
|
|
||||||
{
|
|
||||||
Name = "complex_prompt",
|
|
||||||
Description = "A prompt with arguments",
|
|
||||||
Arguments =
|
|
||||||
[
|
|
||||||
new PromptArgument
|
|
||||||
{
|
{
|
||||||
Name = "temperature",
|
Uri = request.Params.Uri,
|
||||||
Description = "Temperature setting",
|
MimeType = "text/plain",
|
||||||
Required = true
|
Text = $"Dynamic resource {id}: This is a plaintext resource"
|
||||||
},
|
|
||||||
new PromptArgument
|
|
||||||
{
|
|
||||||
Name = "style",
|
|
||||||
Description = "Output style",
|
|
||||||
Required = false
|
|
||||||
}
|
}
|
||||||
],
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
},
|
|
||||||
GetPromptHandler = async (request, cancellationToken) =>
|
|
||||||
{
|
|
||||||
if (request.Params is null)
|
|
||||||
{
|
|
||||||
throw new McpProtocolException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
|
||||||
}
|
|
||||||
List<PromptMessage> messages = [];
|
|
||||||
if (request.Params.Name == "simple_prompt")
|
|
||||||
{
|
|
||||||
messages.Add(new PromptMessage
|
|
||||||
{
|
|
||||||
Role = Role.User,
|
|
||||||
Content = new TextContentBlock { Text = "This is a simple prompt without arguments." },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (request.Params.Name == "complex_prompt")
|
|
||||||
{
|
|
||||||
string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown";
|
|
||||||
string style = request.Params.Arguments?["style"].ToString() ?? "unknown";
|
|
||||||
messages.Add(new PromptMessage
|
|
||||||
{
|
|
||||||
Role = Role.User,
|
|
||||||
Content = new TextContentBlock { Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}" },
|
|
||||||
});
|
|
||||||
messages.Add(new PromptMessage
|
|
||||||
{
|
|
||||||
Role = Role.User,
|
|
||||||
Content = new TextContentBlock { Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?" },
|
|
||||||
});
|
|
||||||
messages.Add(new PromptMessage
|
|
||||||
{
|
|
||||||
Role = Role.User,
|
|
||||||
Content = new ImageContentBlock
|
|
||||||
{
|
|
||||||
Data = System.Text.Encoding.UTF8.GetBytes(MCP_TINY_IMAGE),
|
|
||||||
MimeType = "image/png"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new McpProtocolException($"Unknown prompt: {request.Params.Name}", McpErrorCode.InvalidParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new GetPromptResult
|
|
||||||
{
|
|
||||||
Messages = messages
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ??
|
||||||
|
throw new McpProtocolException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.ResourceNotFound);
|
||||||
|
|
||||||
|
return new ReadResourceResult
|
||||||
|
{
|
||||||
|
Contents = [contents]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
options.Handlers.ListPromptsHandler = async (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
return new ListPromptsResult
|
||||||
|
{
|
||||||
|
Prompts = [
|
||||||
|
new Prompt
|
||||||
|
{
|
||||||
|
Name = "simple_prompt",
|
||||||
|
Description = "A prompt without arguments"
|
||||||
|
},
|
||||||
|
new Prompt
|
||||||
|
{
|
||||||
|
Name = "complex_prompt",
|
||||||
|
Description = "A prompt with arguments",
|
||||||
|
Arguments =
|
||||||
|
[
|
||||||
|
new PromptArgument
|
||||||
|
{
|
||||||
|
Name = "temperature",
|
||||||
|
Description = "Temperature setting",
|
||||||
|
Required = true
|
||||||
|
},
|
||||||
|
new PromptArgument
|
||||||
|
{
|
||||||
|
Name = "style",
|
||||||
|
Description = "Output style",
|
||||||
|
Required = false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
options.Handlers.GetPromptHandler = async (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
if (request.Params is null)
|
||||||
|
{
|
||||||
|
throw new McpProtocolException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PromptMessage> messages = [];
|
||||||
|
|
||||||
|
if (request.Params.Name == "simple_prompt")
|
||||||
|
{
|
||||||
|
messages.Add(new PromptMessage
|
||||||
|
{
|
||||||
|
Role = Role.User,
|
||||||
|
Content = new TextContentBlock { Text = "This is a simple prompt without arguments." },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (request.Params.Name == "complex_prompt")
|
||||||
|
{
|
||||||
|
string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown";
|
||||||
|
string style = request.Params.Arguments?["style"].ToString() ?? "unknown";
|
||||||
|
messages.Add(new PromptMessage
|
||||||
|
{
|
||||||
|
Role = Role.User,
|
||||||
|
Content = new TextContentBlock { Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}" },
|
||||||
|
});
|
||||||
|
messages.Add(new PromptMessage
|
||||||
|
{
|
||||||
|
Role = Role.User,
|
||||||
|
Content = new TextContentBlock { Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?" },
|
||||||
|
});
|
||||||
|
messages.Add(new PromptMessage
|
||||||
|
{
|
||||||
|
Role = Role.User,
|
||||||
|
Content = new ImageContentBlock
|
||||||
|
{
|
||||||
|
Data = System.Text.Encoding.UTF8.GetBytes(MCP_TINY_IMAGE),
|
||||||
|
MimeType = "image/png"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new McpProtocolException($"Unknown prompt: {request.Params.Name}", McpErrorCode.InvalidParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GetPromptResult
|
||||||
|
{
|
||||||
|
Messages = messages
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,24 +23,28 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
List<string> messageTypes = [];
|
List<string> messageTypes = [];
|
||||||
|
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters =>
|
||||||
{
|
{
|
||||||
var logger = GetLogger(context.Services, "MessageFilter1");
|
filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
logger.LogInformation("MessageFilter1 before");
|
{
|
||||||
|
var logger = GetLogger(context.Services, "MessageFilter1");
|
||||||
|
logger.LogInformation("MessageFilter1 before");
|
||||||
|
|
||||||
var messageTypeName = context.JsonRpcMessage.GetType().Name;
|
var messageTypeName = context.JsonRpcMessage.GetType().Name;
|
||||||
messageTypes.Add(messageTypeName);
|
messageTypes.Add(messageTypeName);
|
||||||
|
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
|
|
||||||
logger.LogInformation("MessageFilter1 after");
|
logger.LogInformation("MessageFilter1 after");
|
||||||
})
|
});
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
|
||||||
{
|
filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
var logger = GetLogger(context.Services, "MessageFilter2");
|
{
|
||||||
logger.LogInformation("MessageFilter2 before");
|
var logger = GetLogger(context.Services, "MessageFilter2");
|
||||||
await next(context, cancellationToken);
|
logger.LogInformation("MessageFilter2 before");
|
||||||
logger.LogInformation("MessageFilter2 after");
|
await next(context, cancellationToken);
|
||||||
|
logger.LogInformation("MessageFilter2 after");
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.WithTools<TestTool>()
|
.WithTools<TestTool>()
|
||||||
.WithPrompts<TestPrompt>()
|
.WithPrompts<TestPrompt>()
|
||||||
|
|
@ -69,12 +73,12 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
List<string> messageTypes = [];
|
List<string> messageTypes = [];
|
||||||
|
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var messageTypeName = context.JsonRpcMessage.GetType().Name;
|
var messageTypeName = context.JsonRpcMessage.GetType().Name;
|
||||||
messageTypes.Add(messageTypeName);
|
messageTypes.Add(messageTypeName);
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -91,19 +95,23 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
public async Task AddIncomingMessageFilter_Multiple_Filters_Execute_In_Order()
|
public async Task AddIncomingMessageFilter_Multiple_Filters_Execute_In_Order()
|
||||||
{
|
{
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters =>
|
||||||
{
|
{
|
||||||
var logger = GetLogger(context.Services, "MessageFilter1");
|
filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
logger.LogInformation("MessageFilter1 before");
|
{
|
||||||
await next(context, cancellationToken);
|
var logger = GetLogger(context.Services, "MessageFilter1");
|
||||||
logger.LogInformation("MessageFilter1 after");
|
logger.LogInformation("MessageFilter1 before");
|
||||||
})
|
await next(context, cancellationToken);
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
logger.LogInformation("MessageFilter1 after");
|
||||||
{
|
});
|
||||||
var logger = GetLogger(context.Services, "MessageFilter2");
|
|
||||||
logger.LogInformation("MessageFilter2 before");
|
filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
await next(context, cancellationToken);
|
{
|
||||||
logger.LogInformation("MessageFilter2 after");
|
var logger = GetLogger(context.Services, "MessageFilter2");
|
||||||
|
logger.LogInformation("MessageFilter2 before");
|
||||||
|
await next(context, cancellationToken);
|
||||||
|
logger.LogInformation("MessageFilter2 after");
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
|
|
@ -142,11 +150,11 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
McpServer? capturedServer = null;
|
McpServer? capturedServer = null;
|
||||||
|
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
capturedServer = context.Server;
|
capturedServer = context.Server;
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -166,18 +174,22 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
string? capturedValue = null;
|
string? capturedValue = null;
|
||||||
|
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters =>
|
||||||
{
|
{
|
||||||
context.Items["testKey"] = "testValue";
|
filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
await next(context, cancellationToken);
|
|
||||||
})
|
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
|
||||||
{
|
|
||||||
if (context.Items.TryGetValue("testKey", out var value))
|
|
||||||
{
|
{
|
||||||
capturedValue = value as string;
|
context.Items["testKey"] = "testValue";
|
||||||
}
|
await next(context, cancellationToken);
|
||||||
await next(context, cancellationToken);
|
});
|
||||||
|
|
||||||
|
filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
|
{
|
||||||
|
if (context.Items.TryGetValue("testKey", out var value))
|
||||||
|
{
|
||||||
|
capturedValue = value as string;
|
||||||
|
}
|
||||||
|
await next(context, cancellationToken);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
|
|
@ -196,14 +208,14 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
string? capturedMethod = null;
|
string? capturedMethod = null;
|
||||||
|
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
||||||
{
|
{
|
||||||
capturedMethod = request.Method;
|
capturedMethod = request.Method;
|
||||||
}
|
}
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -219,7 +231,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
public async Task AddIncomingMessageFilter_Exception_Propagates_Properly()
|
public async Task AddIncomingMessageFilter_Exception_Propagates_Properly()
|
||||||
{
|
{
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Only throw for tools/list, not for initialize/initialized
|
// Only throw for tools/list, not for initialize/initialized
|
||||||
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
||||||
|
|
@ -227,7 +239,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
throw new InvalidOperationException("Filter exception");
|
throw new InvalidOperationException("Filter exception");
|
||||||
}
|
}
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -248,19 +260,19 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
var executionOrder = new List<string>();
|
var executionOrder = new List<string>();
|
||||||
|
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
||||||
{
|
{
|
||||||
executionOrder.Add("MessageFilter");
|
executionOrder.Add("MessageFilter");
|
||||||
}
|
}
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
.WithRequestFilters(filters => filters.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
executionOrder.Add("ListToolsFilter");
|
executionOrder.Add("ListToolsFilter");
|
||||||
return await next(request, cancellationToken);
|
return await next(request, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -279,7 +291,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
public async Task AddIncomingMessageFilter_Can_Skip_Default_Handlers()
|
public async Task AddIncomingMessageFilter_Can_Skip_Default_Handlers()
|
||||||
{
|
{
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Skip calling next for tools/list
|
// Skip calling next for tools/list
|
||||||
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
||||||
|
|
@ -288,7 +300,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -310,7 +322,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
var observedMessages = new List<string>();
|
var observedMessages = new List<string>();
|
||||||
|
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddOutgoingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddOutgoingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
switch (context.JsonRpcMessage)
|
switch (context.JsonRpcMessage)
|
||||||
{
|
{
|
||||||
|
|
@ -330,7 +342,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
}
|
}
|
||||||
|
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<ProgressTool>();
|
.WithTools<ProgressTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -353,7 +365,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
public async Task AddOutgoingMessageFilter_Can_Skip_Sending_Messages()
|
public async Task AddOutgoingMessageFilter_Can_Skip_Sending_Messages()
|
||||||
{
|
{
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddOutgoingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddOutgoingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
if (context.JsonRpcMessage is JsonRpcResponse response && response.Result is JsonObject result && result.ContainsKey("tools"))
|
if (context.JsonRpcMessage is JsonRpcResponse response && response.Result is JsonObject result && result.ContainsKey("tools"))
|
||||||
{
|
{
|
||||||
|
|
@ -361,7 +373,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
}
|
}
|
||||||
|
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -379,7 +391,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
public async Task AddOutgoingMessageFilter_Can_Send_Additional_Messages()
|
public async Task AddOutgoingMessageFilter_Can_Send_Additional_Messages()
|
||||||
{
|
{
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddOutgoingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddOutgoingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
if (context.JsonRpcMessage is JsonRpcResponse response && response.Result is JsonObject result && result.ContainsKey("tools"))
|
if (context.JsonRpcMessage is JsonRpcResponse response && response.Result is JsonObject result && result.ContainsKey("tools"))
|
||||||
{
|
{
|
||||||
|
|
@ -394,7 +406,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
}
|
}
|
||||||
|
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -422,7 +434,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
string? capturedValue = null;
|
string? capturedValue = null;
|
||||||
|
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Set an item in the message filter
|
// Set an item in the message filter
|
||||||
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
||||||
|
|
@ -430,8 +442,8 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
context.Items["messageFilterKey"] = "messageFilterValue";
|
context.Items["messageFilterKey"] = "messageFilterValue";
|
||||||
}
|
}
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
.WithRequestFilters(filters => filters.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Read the item in the request-specific filter
|
// Read the item in the request-specific filter
|
||||||
if (request.Items.TryGetValue("messageFilterKey", out var value))
|
if (request.Items.TryGetValue("messageFilterKey", out var value))
|
||||||
|
|
@ -439,7 +451,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
capturedValue = value as string;
|
capturedValue = value as string;
|
||||||
}
|
}
|
||||||
return await next(request, cancellationToken);
|
return await next(request, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -457,7 +469,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
object? capturedValue = null;
|
object? capturedValue = null;
|
||||||
|
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Set an item in the message filter for CallTool requests
|
// Set an item in the message filter for CallTool requests
|
||||||
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsCall)
|
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsCall)
|
||||||
|
|
@ -465,8 +477,8 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
context.Items["toolContextKey"] = 42;
|
context.Items["toolContextKey"] = 42;
|
||||||
}
|
}
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.AddCallToolFilter((next) => async (request, cancellationToken) =>
|
.WithRequestFilters(filters => filters.AddCallToolFilter((next) => async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Read the item in the call tool filter
|
// Read the item in the call tool filter
|
||||||
if (request.Items.TryGetValue("toolContextKey", out var value))
|
if (request.Items.TryGetValue("toolContextKey", out var value))
|
||||||
|
|
@ -474,7 +486,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
capturedValue = value;
|
capturedValue = value;
|
||||||
}
|
}
|
||||||
return await next(request, cancellationToken);
|
return await next(request, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<SimpleTool>();
|
.WithTools<SimpleTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -492,7 +504,7 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
ClaimsPrincipal? capturedUser = null;
|
ClaimsPrincipal? capturedUser = null;
|
||||||
|
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Set a custom user in the message filter for CallTool requests
|
// Set a custom user in the message filter for CallTool requests
|
||||||
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsCall)
|
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsCall)
|
||||||
|
|
@ -502,13 +514,13 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
context.User = new ClaimsPrincipal(identity);
|
context.User = new ClaimsPrincipal(identity);
|
||||||
}
|
}
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.AddCallToolFilter((next) => async (request, cancellationToken) =>
|
.WithRequestFilters(filters => filters.AddCallToolFilter((next) => async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Read the user in the call tool filter
|
// Read the user in the call tool filter
|
||||||
capturedUser = request.User;
|
capturedUser = request.User;
|
||||||
return await next(request, cancellationToken);
|
return await next(request, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<SimpleTool>();
|
.WithTools<SimpleTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -529,43 +541,47 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
object? secondFilterValue = null;
|
object? secondFilterValue = null;
|
||||||
|
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters =>
|
||||||
{
|
{
|
||||||
// First filter sets an item
|
filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
|
||||||
{
|
{
|
||||||
context.Items["firstFilterKey"] = "firstFilterValue";
|
// First filter sets an item
|
||||||
}
|
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
||||||
await next(context, cancellationToken);
|
|
||||||
})
|
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
|
||||||
{
|
|
||||||
// Second filter creates a new context with a new JsonRpcRequest and adds an item
|
|
||||||
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
|
||||||
{
|
|
||||||
var newRequest = new JsonRpcRequest
|
|
||||||
{
|
{
|
||||||
Id = request.Id,
|
context.Items["firstFilterKey"] = "firstFilterValue";
|
||||||
Method = RequestMethods.ToolsList,
|
}
|
||||||
Params = request.Params,
|
await next(context, cancellationToken);
|
||||||
Context = new JsonRpcMessageContext { RelatedTransport = request.Context?.RelatedTransport },
|
});
|
||||||
};
|
|
||||||
|
|
||||||
var newContext = new MessageContext(context.Server, newRequest);
|
filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
newContext.Items["secondFilterKey"] = "secondFilterValue";
|
{
|
||||||
|
// Second filter creates a new context with a new JsonRpcRequest and adds an item
|
||||||
|
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
||||||
|
{
|
||||||
|
var newRequest = new JsonRpcRequest
|
||||||
|
{
|
||||||
|
Id = request.Id,
|
||||||
|
Method = RequestMethods.ToolsList,
|
||||||
|
Params = request.Params,
|
||||||
|
Context = new JsonRpcMessageContext { RelatedTransport = request.Context?.RelatedTransport },
|
||||||
|
};
|
||||||
|
|
||||||
await next(newContext, cancellationToken);
|
var newContext = new MessageContext(context.Server, newRequest);
|
||||||
return;
|
newContext.Items["secondFilterKey"] = "secondFilterValue";
|
||||||
}
|
|
||||||
await next(context, cancellationToken);
|
await next(newContext, cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await next(context, cancellationToken);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
.WithRequestFilters(filters => filters.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Request filter should see items from message filters
|
// Request filter should see items from message filters
|
||||||
request.Items.TryGetValue("firstFilterKey", out firstFilterValue);
|
request.Items.TryGetValue("firstFilterKey", out firstFilterValue);
|
||||||
request.Items.TryGetValue("secondFilterKey", out secondFilterValue);
|
request.Items.TryGetValue("secondFilterKey", out secondFilterValue);
|
||||||
return await next(request, cancellationToken);
|
return await next(request, cancellationToken);
|
||||||
})
|
}))
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
StartServer();
|
StartServer();
|
||||||
|
|
@ -584,32 +600,36 @@ public class McpServerBuilderExtensionsMessageFilterTests(ITestOutputHelper test
|
||||||
var observedValues = new List<string>();
|
var observedValues = new List<string>();
|
||||||
|
|
||||||
McpServerBuilder
|
McpServerBuilder
|
||||||
.AddIncomingMessageFilter((next) => async (context, cancellationToken) =>
|
.WithMessageFilters(filters => filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
|
||||||
{
|
{
|
||||||
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
if (context.JsonRpcMessage is JsonRpcRequest request && request.Method == RequestMethods.ToolsList)
|
||||||
{
|
{
|
||||||
context.Items["sharedKey"] = "fromMessageFilter";
|
context.Items["sharedKey"] = "fromMessageFilter";
|
||||||
}
|
}
|
||||||
await next(context, cancellationToken);
|
await next(context, cancellationToken);
|
||||||
})
|
}))
|
||||||
.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
.WithRequestFilters(filters =>
|
||||||
{
|
{
|
||||||
// First request filter reads and modifies
|
filters.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
||||||
if (request.Items.TryGetValue("sharedKey", out var value))
|
|
||||||
{
|
{
|
||||||
observedValues.Add((string)value!);
|
// First request filter reads and modifies
|
||||||
request.Items["sharedKey"] = "modifiedByFilter1";
|
if (request.Items.TryGetValue("sharedKey", out var value))
|
||||||
}
|
{
|
||||||
return await next(request, cancellationToken);
|
observedValues.Add((string)value!);
|
||||||
})
|
request.Items["sharedKey"] = "modifiedByFilter1";
|
||||||
.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
}
|
||||||
{
|
return await next(request, cancellationToken);
|
||||||
// Second request filter should see modified value
|
});
|
||||||
if (request.Items.TryGetValue("sharedKey", out var value))
|
|
||||||
|
filters.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
observedValues.Add((string)value!);
|
// Second request filter should see modified value
|
||||||
}
|
if (request.Items.TryGetValue("sharedKey", out var value))
|
||||||
return await next(request, cancellationToken);
|
{
|
||||||
|
observedValues.Add((string)value!);
|
||||||
|
}
|
||||||
|
return await next(request, cancellationToken);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.WithTools<TestTool>();
|
.WithTools<TestTool>();
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue