Add AspNetCoreSseServer sample (#81)
* Add AspNetCoreSseMcpTransport sample * Fix build errors after rebase * React to McpServerFactory changes * Make Utf8JsonWriter non thread static * Fix test errors caused by not rebasing the HttpLister changes on factory overhaul commit
This commit is contained in:
parent
b5a26e4c33
commit
90dfc35fbd
4 changed files with 635 additions and 332 deletions
|
|
@ -41,12 +41,10 @@ internal class Program
|
|||
ProtocolVersion = "2024-11-05",
|
||||
ServerInstructions = "This is a test server with only stub functionality"
|
||||
};
|
||||
var loggerFactory = CreateLoggerFactory();
|
||||
|
||||
McpServerFactory factory = new McpServerFactory(new HttpListenerSseServerTransport("TestServer", 3001, loggerFactory), options, loggerFactory);
|
||||
IMcpServer server = factory.CreateServer();
|
||||
IMcpServer? server = null;
|
||||
|
||||
Console.WriteLine("Server object created, registering handlers.");
|
||||
Console.WriteLine("Registering handlers.");
|
||||
|
||||
#region Helped method
|
||||
static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100)
|
||||
|
|
@ -70,85 +68,6 @@ internal class Program
|
|||
}
|
||||
#endregion
|
||||
|
||||
#region Tools
|
||||
server.SetListToolsHandler((request, cancellationToken) =>
|
||||
{
|
||||
return Task.FromResult(new ListToolsResult()
|
||||
{
|
||||
Tools =
|
||||
[
|
||||
new Tool()
|
||||
{
|
||||
Name = "echo",
|
||||
Description = "Echoes the input back to the client.",
|
||||
InputSchema = new JsonSchema()
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, JsonSchemaProperty>()
|
||||
{
|
||||
["message"] = new JsonSchemaProperty() { Type = "string", Description = "The input to echo back." }
|
||||
}
|
||||
},
|
||||
},
|
||||
new Tool()
|
||||
{
|
||||
Name = "sampleLLM",
|
||||
Description = "Samples from an LLM using MCP's sampling feature.",
|
||||
InputSchema = new JsonSchema()
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, JsonSchemaProperty>()
|
||||
{
|
||||
["prompt"] = new JsonSchemaProperty() { Type = "string", Description = "The prompt to send to the LLM" },
|
||||
["maxTokens"] = new JsonSchemaProperty() { Type = "number", Description = "Maximum number of tokens to generate" }
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
server.SetCallToolHandler(async (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params is null)
|
||||
{
|
||||
throw new McpServerException("Missing required parameter 'name'");
|
||||
}
|
||||
if (request.Params.Name == "echo")
|
||||
{
|
||||
if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
||||
{
|
||||
throw new McpServerException("Missing required argument 'message'");
|
||||
}
|
||||
return new CallToolResponse()
|
||||
{
|
||||
Content = [new Content() { Text = "Echo: " + message.ToString(), Type = "text" }]
|
||||
};
|
||||
}
|
||||
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 McpServerException("Missing required arguments 'prompt' and 'maxTokens'");
|
||||
}
|
||||
var sampleResult = await server.RequestSamplingAsync(CreateRequestSamplingParams(prompt?.ToString() ?? "", "sampleLLM", Convert.ToInt32(maxTokens?.ToString())),
|
||||
cancellationToken);
|
||||
|
||||
return new CallToolResponse()
|
||||
{
|
||||
Content = [new Content() { Text = $"LLM sampling result: {sampleResult.Content.Text}", Type = "text" }]
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new McpServerException($"Unknown tool: {request.Params.Name}");
|
||||
}
|
||||
});
|
||||
#endregion
|
||||
|
||||
#region Resources
|
||||
List<Resource> resources = new();
|
||||
List<ResourceContents> resourceContents = new();
|
||||
for (int i = 0; i < 100; ++i)
|
||||
|
|
@ -189,156 +108,236 @@ internal class Program
|
|||
|
||||
const int pageSize = 10;
|
||||
|
||||
server.SetListResourcesHandler((request, cancellationToken) =>
|
||||
options.Capabilities = new()
|
||||
{
|
||||
int startIndex = 0;
|
||||
var requestParams = request.Params ?? new();
|
||||
if (requestParams.Cursor is not null)
|
||||
Tools = new()
|
||||
{
|
||||
try
|
||||
ListToolsHandler = (request, cancellationToken) =>
|
||||
{
|
||||
var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(requestParams.Cursor));
|
||||
startIndex = Convert.ToInt32(startIndexAsString);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new McpServerException("Invalid cursor");
|
||||
}
|
||||
}
|
||||
|
||||
int endIndex = Math.Min(startIndex + pageSize, resources.Count);
|
||||
string? nextCursor = null;
|
||||
|
||||
if (endIndex < resources.Count)
|
||||
{
|
||||
nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString()));
|
||||
}
|
||||
return Task.FromResult(new ListResourcesResult()
|
||||
{
|
||||
NextCursor = nextCursor,
|
||||
Resources = resources.GetRange(startIndex, endIndex - startIndex)
|
||||
});
|
||||
});
|
||||
|
||||
server.SetReadResourceHandler((request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params?.Uri is null)
|
||||
{
|
||||
throw new McpServerException("Missing required argument 'uri'");
|
||||
}
|
||||
ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri);
|
||||
if (contents is null)
|
||||
{
|
||||
throw new McpServerException("Resource not found");
|
||||
}
|
||||
return Task.FromResult(new ReadResourceResult()
|
||||
{
|
||||
Contents = [contents]
|
||||
});
|
||||
});
|
||||
#endregion
|
||||
|
||||
#region Prompts
|
||||
server.SetListPromptsHandler((request, cancellationToken) =>
|
||||
{
|
||||
return Task.FromResult(new ListPromptsResult()
|
||||
{
|
||||
Prompts = [
|
||||
new Prompt()
|
||||
return Task.FromResult(new ListToolsResult()
|
||||
{
|
||||
Name = "simple_prompt",
|
||||
Description = "A prompt without arguments"
|
||||
},
|
||||
new Prompt()
|
||||
{
|
||||
Name = "complex_prompt",
|
||||
Description = "A prompt with arguments",
|
||||
Arguments = new()
|
||||
{
|
||||
new PromptArgument()
|
||||
Tools =
|
||||
[
|
||||
new Tool()
|
||||
{
|
||||
Name = "temperature",
|
||||
Description = "Temperature setting",
|
||||
Required = true
|
||||
Name = "echo",
|
||||
Description = "Echoes the input back to the client.",
|
||||
InputSchema = new JsonSchema()
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, JsonSchemaProperty>()
|
||||
{
|
||||
["message"] = new JsonSchemaProperty() { Type = "string", Description = "The input to echo back." }
|
||||
}
|
||||
},
|
||||
},
|
||||
new PromptArgument()
|
||||
new Tool()
|
||||
{
|
||||
Name = "style",
|
||||
Description = "Output style",
|
||||
Required = false
|
||||
Name = "sampleLLM",
|
||||
Description = "Samples from an LLM using MCP's sampling feature.",
|
||||
InputSchema = new JsonSchema()
|
||||
{
|
||||
Type = "object",
|
||||
Properties = new Dictionary<string, JsonSchemaProperty>()
|
||||
{
|
||||
["prompt"] = new JsonSchemaProperty() { Type = "string", Description = "The prompt to send to the LLM" },
|
||||
["maxTokens"] = new JsonSchemaProperty() { Type = "number", Description = "Maximum number of tokens to generate" }
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
CallToolHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params is null)
|
||||
{
|
||||
throw new McpServerException("Missing required parameter 'name'");
|
||||
}
|
||||
if (request.Params.Name == "echo")
|
||||
{
|
||||
if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
||||
{
|
||||
throw new McpServerException("Missing required argument 'message'");
|
||||
}
|
||||
return new CallToolResponse()
|
||||
{
|
||||
Content = [new Content() { Text = "Echo: " + message.ToString(), Type = "text" }]
|
||||
};
|
||||
}
|
||||
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 McpServerException("Missing required arguments 'prompt' and 'maxTokens'");
|
||||
}
|
||||
var sampleResult = await server!.RequestSamplingAsync(CreateRequestSamplingParams(prompt?.ToString() ?? "", "sampleLLM", Convert.ToInt32(maxTokens?.ToString())),
|
||||
cancellationToken);
|
||||
|
||||
return new CallToolResponse()
|
||||
{
|
||||
Content = [new Content() { Text = $"LLM sampling result: {sampleResult.Content.Text}", Type = "text" }]
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new McpServerException($"Unknown tool: {request.Params.Name}");
|
||||
}
|
||||
}
|
||||
},
|
||||
Resources = new()
|
||||
{
|
||||
ListResourcesHandler = (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
|
||||
{
|
||||
throw new McpServerException("Invalid cursor");
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
int endIndex = Math.Min(startIndex + pageSize, resources.Count);
|
||||
string? nextCursor = null;
|
||||
|
||||
if (endIndex < resources.Count)
|
||||
{
|
||||
nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString()));
|
||||
}
|
||||
return Task.FromResult(new ListResourcesResult()
|
||||
{
|
||||
NextCursor = nextCursor,
|
||||
Resources = resources.GetRange(startIndex, endIndex - startIndex)
|
||||
});
|
||||
},
|
||||
ReadResourceHandler =(request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params?.Uri is null)
|
||||
{
|
||||
throw new McpServerException("Missing required argument 'uri'");
|
||||
}
|
||||
ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri);
|
||||
if (contents is null)
|
||||
{
|
||||
throw new McpServerException("Resource not found");
|
||||
}
|
||||
return Task.FromResult(new ReadResourceResult()
|
||||
{
|
||||
Contents = [contents]
|
||||
});
|
||||
}
|
||||
},
|
||||
Prompts = new()
|
||||
{
|
||||
ListPromptsHandler = (request, cancellationToken) =>
|
||||
{
|
||||
return Task.FromResult(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()
|
||||
{
|
||||
new PromptArgument()
|
||||
{
|
||||
Name = "temperature",
|
||||
Description = "Temperature setting",
|
||||
Required = true
|
||||
},
|
||||
new PromptArgument()
|
||||
{
|
||||
Name = "style",
|
||||
Description = "Output style",
|
||||
Required = false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
GetPromptHandler = (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params is null)
|
||||
{
|
||||
throw new McpServerException("Missing required parameter 'name'");
|
||||
}
|
||||
List<PromptMessage> messages = new();
|
||||
if (request.Params.Name == "simple_prompt")
|
||||
{
|
||||
messages.Add(new PromptMessage()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
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 Content()
|
||||
{
|
||||
Type = "text",
|
||||
Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}"
|
||||
}
|
||||
});
|
||||
messages.Add(new PromptMessage()
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
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 Content()
|
||||
{
|
||||
Type = "image",
|
||||
Data = MCP_TINY_IMAGE,
|
||||
MimeType = "image/png"
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new McpServerException($"Unknown prompt: {request.Params.Name}");
|
||||
}
|
||||
|
||||
server.SetGetPromptHandler((request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params is null)
|
||||
{
|
||||
throw new McpServerException("Missing required parameter 'name'");
|
||||
}
|
||||
List<PromptMessage> messages = new();
|
||||
if (request.Params.Name == "simple_prompt")
|
||||
{
|
||||
messages.Add(new PromptMessage()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content()
|
||||
return Task.FromResult(new GetPromptResult()
|
||||
{
|
||||
Type = "text",
|
||||
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 Content()
|
||||
{
|
||||
Type = "text",
|
||||
Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}"
|
||||
}
|
||||
});
|
||||
messages.Add(new PromptMessage()
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
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 Content()
|
||||
{
|
||||
Type = "image",
|
||||
Data = MCP_TINY_IMAGE,
|
||||
MimeType = "image/png"
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new McpServerException($"Unknown prompt: {request.Params.Name}");
|
||||
}
|
||||
Messages = messages
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return Task.FromResult(new GetPromptResult()
|
||||
{
|
||||
Messages = messages
|
||||
});
|
||||
});
|
||||
#endregion
|
||||
|
||||
#region Sampling
|
||||
#endregion
|
||||
var loggerFactory = CreateLoggerFactory();
|
||||
server = McpServerFactory.Create(new HttpListenerSseServerTransport("TestServer", 3001, loggerFactory), options, loggerFactory);
|
||||
|
||||
Console.WriteLine("Server initialized.");
|
||||
|
||||
|
|
|
|||
|
|
@ -280,9 +280,9 @@ public class McpServerTests
|
|||
await Can_Handle_Requests(
|
||||
serverCapabilities: null,
|
||||
method: "completion/complete",
|
||||
configureOptions: options => options with
|
||||
configureOptions: options =>
|
||||
{
|
||||
GetCompletionHandler = (request, ct) =>
|
||||
options.GetCompletionHandler = (request, ct) =>
|
||||
Task.FromResult(new CompleteResult
|
||||
{
|
||||
Completion = new()
|
||||
|
|
@ -291,7 +291,7 @@ public class McpServerTests
|
|||
Total = 2,
|
||||
HasMore = true
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
assertResult: response =>
|
||||
{
|
||||
|
|
@ -518,14 +518,11 @@ public class McpServerTests
|
|||
await Throws_Exception_If_No_Handler_Assigned(new ServerCapabilities { Tools = new() }, "tools/call", "CallTool handler not configured");
|
||||
}
|
||||
|
||||
private async Task Can_Handle_Requests(ServerCapabilities? serverCapabilities, string method, Func<McpServerOptions, McpServerOptions>? configureOptions, Action<object> assertResult)
|
||||
private async Task Can_Handle_Requests(ServerCapabilities? serverCapabilities, string method, Action<McpServerOptions>? configureOptions, Action<object> assertResult)
|
||||
{
|
||||
await using var transport = new TestServerTransport();
|
||||
var options = CreateOptions(serverCapabilities);
|
||||
if (configureOptions is not null)
|
||||
{
|
||||
options = configureOptions(options);
|
||||
}
|
||||
configureOptions?.Invoke(options);
|
||||
|
||||
await using var server = new McpServer(transport, options, _loggerFactory.Object, _serviceProvider);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ public class SseServerIntegrationTestFixture : IDisposable
|
|||
private Process _process;
|
||||
|
||||
public ILoggerFactory LoggerFactory { get; }
|
||||
public McpClientFactory Factory { get; }
|
||||
public McpClientOptions DefaultOptions { get; }
|
||||
public McpServerConfig DefaultConfig { get; }
|
||||
|
||||
|
|
@ -24,7 +23,6 @@ public class SseServerIntegrationTestFixture : IDisposable
|
|||
DefaultOptions = new()
|
||||
{
|
||||
ClientInfo = new() { Name = "IntegrationTestClient", Version = "1.0.0" },
|
||||
Capabilities = new() { Sampling = new(), Roots = new() }
|
||||
};
|
||||
|
||||
DefaultConfig = new McpServerConfig
|
||||
|
|
@ -36,13 +34,6 @@ public class SseServerIntegrationTestFixture : IDisposable
|
|||
Location = "http://localhost:3001/sse"
|
||||
};
|
||||
|
||||
// Inject the mock transport into the factory
|
||||
Factory = new McpClientFactory(
|
||||
[DefaultConfig],
|
||||
DefaultOptions,
|
||||
LoggerFactory
|
||||
);
|
||||
|
||||
Start();
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +61,7 @@ public class SseServerIntegrationTestFixture : IDisposable
|
|||
{
|
||||
try
|
||||
{
|
||||
var client = Factory.GetClientAsync("test_server").Result;
|
||||
var client = McpClientFactory.CreateAsync(DefaultConfig, DefaultOptions, loggerFactory: LoggerFactory).GetAwaiter().GetResult();
|
||||
client.DisposeAsync().AsTask().Wait();
|
||||
LoggerFactory?.Dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,21 @@ public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestF
|
|||
_fixture = fixture;
|
||||
}
|
||||
|
||||
private Task<IMcpClient> GetClientAsync(McpClientOptions? options = null)
|
||||
{
|
||||
return McpClientFactory.CreateAsync(
|
||||
_fixture.DefaultConfig,
|
||||
options ?? _fixture.DefaultOptions,
|
||||
loggerFactory: _fixture.LoggerFactory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConnectAndPing_Sse_TestServer()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
var client = await _fixture.Factory.GetClientAsync("test_server");
|
||||
var client = await GetClientAsync();
|
||||
await client.PingAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
|
|
@ -31,7 +39,7 @@ public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestF
|
|||
// Arrange
|
||||
|
||||
// Act
|
||||
var client = await _fixture.Factory.GetClientAsync("test_server");
|
||||
var client = await GetClientAsync();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(client.ServerCapabilities);
|
||||
|
|
@ -44,7 +52,7 @@ public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestF
|
|||
// arrange
|
||||
|
||||
// act
|
||||
var client = await _fixture.Factory.GetClientAsync("test_server");
|
||||
var client = await GetClientAsync();
|
||||
var tools = await client.ListToolsAsync().ToListAsync();
|
||||
|
||||
// assert
|
||||
|
|
@ -57,7 +65,7 @@ public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestF
|
|||
// arrange
|
||||
|
||||
// act
|
||||
var client = await _fixture.Factory.GetClientAsync("test_server");
|
||||
var client = await GetClientAsync();
|
||||
var result = await client.CallToolAsync(
|
||||
"echo",
|
||||
new Dictionary<string, object>
|
||||
|
|
@ -80,7 +88,7 @@ public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestF
|
|||
// arrange
|
||||
|
||||
// act
|
||||
var client = await _fixture.Factory.GetClientAsync("test_server");
|
||||
var client = await GetClientAsync();
|
||||
|
||||
List<Resource> allResources = [];
|
||||
string? cursor = null;
|
||||
|
|
@ -102,7 +110,7 @@ public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestF
|
|||
// arrange
|
||||
|
||||
// act
|
||||
var client = await _fixture.Factory.GetClientAsync("test_server");
|
||||
var client = await GetClientAsync();
|
||||
// Odd numbered resources are text in the everything server (despite the docs saying otherwise)
|
||||
// 1 is index 0, which is "even" in the 0-based index
|
||||
// We copied this oddity to the test server
|
||||
|
|
@ -119,7 +127,7 @@ public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestF
|
|||
// arrange
|
||||
|
||||
// act
|
||||
var client = await _fixture.Factory.GetClientAsync("test_server");
|
||||
var client = await GetClientAsync();
|
||||
// Even numbered resources are binary in the everything server (despite the docs saying otherwise)
|
||||
// 2 is index 1, which is "odd" in the 0-based index
|
||||
// We copied this oddity to the test server
|
||||
|
|
@ -136,7 +144,7 @@ public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestF
|
|||
// arrange
|
||||
|
||||
// act
|
||||
var client = await _fixture.Factory.GetClientAsync("test_server");
|
||||
var client = await GetClientAsync();
|
||||
var prompts = await client.ListPromptsAsync().ToListAsync();
|
||||
|
||||
// assert
|
||||
|
|
@ -153,7 +161,7 @@ public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestF
|
|||
// arrange
|
||||
|
||||
// act
|
||||
var client = await _fixture.Factory.GetClientAsync("test_server");
|
||||
var client = await GetClientAsync();
|
||||
var result = await client.GetPromptAsync("simple_prompt", null, CancellationToken.None);
|
||||
|
||||
// assert
|
||||
|
|
@ -167,7 +175,7 @@ public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestF
|
|||
// arrange
|
||||
|
||||
// act
|
||||
var client = await _fixture.Factory.GetClientAsync("test_server");
|
||||
var client = await GetClientAsync();
|
||||
var arguments = new Dictionary<string, object>
|
||||
{
|
||||
{ "temperature", "0.7" },
|
||||
|
|
@ -186,7 +194,7 @@ public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestF
|
|||
// arrange
|
||||
|
||||
// act
|
||||
var client = await _fixture.Factory.GetClientAsync("test_server");
|
||||
var client = await GetClientAsync();
|
||||
await Assert.ThrowsAsync<McpClientException>(() =>
|
||||
client.GetPromptAsync("non_existent_prompt", null, CancellationToken.None));
|
||||
}
|
||||
|
|
@ -194,26 +202,34 @@ public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestF
|
|||
[Fact]
|
||||
public async Task Sampling_Sse_TestServer()
|
||||
{
|
||||
// arrange
|
||||
var client = await _fixture.Factory.GetClientAsync("test_server");
|
||||
|
||||
// arrange
|
||||
// Set up the sampling handler
|
||||
int samplingHandlerCalls = 0;
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
client.SetSamplingHandler(async (_, _) =>
|
||||
var options = _fixture.DefaultOptions with
|
||||
{
|
||||
samplingHandlerCalls++;
|
||||
return new CreateMessageResult
|
||||
Capabilities = new()
|
||||
{
|
||||
Model = "test-model",
|
||||
Role = "assistant",
|
||||
Content = new Content
|
||||
Sampling = new()
|
||||
{
|
||||
Type = "text",
|
||||
Text = "Test response"
|
||||
SamplingHandler = async (_, _) =>
|
||||
{
|
||||
samplingHandlerCalls++;
|
||||
return new CreateMessageResult
|
||||
{
|
||||
Model = "test-model",
|
||||
Role = "assistant",
|
||||
Content = new Content
|
||||
{
|
||||
Type = "text",
|
||||
Text = "Test response"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
var client = await GetClientAsync(options);
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
// Call the server's sampleLLM tool which should trigger our sampling handler
|
||||
|
|
|
|||
Loading…
Reference in a new issue