db620d33df
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
77 lines
2.8 KiB
C#
77 lines
2.8 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO.Pipelines;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using ModelContextProtocol.Client;
|
|
using ModelContextProtocol.Protocol;
|
|
using ModelContextProtocol.Server;
|
|
|
|
namespace Microsoft.Agents.AI.Skills.Mcp.UnitTests;
|
|
|
|
/// <summary>
|
|
/// Spins up an in-memory MCP server hosting a configurable set of resources, and returns an
|
|
/// <see cref="McpClient"/> connected to it. Disposes both ends together.
|
|
/// </summary>
|
|
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by AgentMcpSkillsSourceTests which is temporarily excluded from compilation.")]
|
|
internal sealed class InMemoryMcpServer : IAsyncDisposable
|
|
{
|
|
private readonly Pipe _clientToServerPipe = new();
|
|
private readonly Pipe _serverToClientPipe = new();
|
|
private readonly CancellationTokenSource _cts = new();
|
|
private readonly ServiceProvider _serviceProvider;
|
|
private readonly Task _serverTask;
|
|
|
|
public InMemoryMcpServer(Action<IMcpServerBuilder> configure)
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance));
|
|
|
|
IMcpServerBuilder builder = services
|
|
.AddMcpServer()
|
|
.WithStreamServerTransport(
|
|
inputStream: this._clientToServerPipe.Reader.AsStream(),
|
|
outputStream: this._serverToClientPipe.Writer.AsStream());
|
|
|
|
configure(builder);
|
|
|
|
this._serviceProvider = services.BuildServiceProvider();
|
|
var server = this._serviceProvider.GetRequiredService<McpServer>();
|
|
this._serverTask = server.RunAsync(this._cts.Token);
|
|
}
|
|
|
|
public async Task<McpClient> CreateClientAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
return await McpClient.CreateAsync(
|
|
new StreamClientTransport(
|
|
serverInput: this._clientToServerPipe.Writer.AsStream(),
|
|
serverOutput: this._serverToClientPipe.Reader.AsStream()),
|
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
await this._cts.CancelAsync().ConfigureAwait(false);
|
|
|
|
this._clientToServerPipe.Writer.Complete();
|
|
this._serverToClientPipe.Writer.Complete();
|
|
|
|
try
|
|
{
|
|
await this._serverTask.ConfigureAwait(false);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// Expected when the server is cancelled during shutdown.
|
|
}
|
|
|
|
await this._serviceProvider.DisposeAsync().ConfigureAwait(false);
|
|
this._cts.Dispose();
|
|
}
|
|
}
|