Skip to content

Commit 3c274ff

Browse files
authored
feat: Create an MCP server (#2)
* fix: Improve attribute handling * feat: Prepare MCP * Delete server.yaml
1 parent e207248 commit 3c274ff

File tree

21 files changed

+685
-95
lines changed

21 files changed

+685
-95
lines changed

Directory.Packages.props

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
88
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
99
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
10+
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.7.0" />
1011
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
1112
<PackageVersion Include="Microsoft.OpenApi.YamlReader" Version="2.0.0" />
1213
<PackageVersion Include="Microsoft.OpenApi" Version="2.0.0" />
1314
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
15+
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" />
16+
<PackageVersion Include="ModelContextProtocol" Version="0.3.0-preview.3" />
17+
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.3.0-preview.3" />
1418
<PackageVersion Include="NSubstitute" Version="5.1.0" />
1519
<PackageVersion Include="PolySharp" Version="1.15.0" />
1620
<PackageVersion Include="SharpYaml" Version="2.1.3" />
@@ -25,6 +29,12 @@
2529
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.7" />
2630
<PackageVersion Include="System.Text.Json" Version="9.0.7" />
2731
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
32+
<PackageVersion Include="OpenTelemetry" Version="1.12.0" />
33+
<PackageVersion Include="OpenTelemetry.Exporter.InMemory" Version="1.12.0" />
34+
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
35+
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
36+
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
37+
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
2838
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.3" />
2939
<PackageVersion Include="xunit" Version="2.9.3" />
3040
</ItemGroup>

OpenApi.Client.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApi.Client.SourceGenera
2727
EndProject
2828
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApi.Client.Cli.UnitTests", "tests\OpenApi.Client.Cli.UnitTests\OpenApi.Client.Cli.UnitTests.csproj", "{0C407F0F-BE50-4850-B695-FE1C0910E8BB}"
2929
EndProject
30+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApi.Client.Mcp", "src\OpenApi.Client.Mcp\OpenApi.Client.Mcp.csproj", "{BA946406-F267-4996-8769-E5B607E18CB4}"
31+
EndProject
3032
Global
3133
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3234
Debug|Any CPU = Debug|Any CPU
@@ -53,6 +55,10 @@ Global
5355
{0C407F0F-BE50-4850-B695-FE1C0910E8BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
5456
{0C407F0F-BE50-4850-B695-FE1C0910E8BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
5557
{0C407F0F-BE50-4850-B695-FE1C0910E8BB}.Release|Any CPU.Build.0 = Release|Any CPU
58+
{BA946406-F267-4996-8769-E5B607E18CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
59+
{BA946406-F267-4996-8769-E5B607E18CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
60+
{BA946406-F267-4996-8769-E5B607E18CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
61+
{BA946406-F267-4996-8769-E5B607E18CB4}.Release|Any CPU.Build.0 = Release|Any CPU
5662
EndGlobalSection
5763
GlobalSection(SolutionProperties) = preSolution
5864
HideSolutionNode = FALSE
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// This Source Code Form is subject to the terms of the MIT License.
2+
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3+
// Copyright (C) Leszek Pomianowski and OpenAPI Client Contributors.
4+
// All Rights Reserved.
5+
6+
namespace OpenApi.Client.Mcp.Configuration;
7+
8+
internal enum McpMode
9+
{
10+
Stdio,
11+
Http,
12+
Both,
13+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// This Source Code Form is subject to the terms of the MIT License.
2+
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3+
// Copyright (C) Leszek Pomianowski and OpenAPI Client Contributors.
4+
// All Rights Reserved.
5+
6+
namespace OpenApi.Client.Mcp.Configuration;
7+
8+
internal sealed class ServerOptions
9+
{
10+
public McpMode Mode { get; set; } = McpMode.Stdio;
11+
}

src/OpenApi.Client.Mcp/Dockerfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
2+
3+
WORKDIR /src
4+
COPY . .
5+
6+
RUN find . -type f -name 'appsettings.*.json' ! -name 'appsettings.json' -exec rm -f {} +
7+
RUN find . -name 'launchSettings.json' -exec rm -f {} +
8+
9+
RUN dotnet publish "src/OpenApi.Client.Mcp/OpenApi.Client.Mcp.csproj" -c Release -o /app/ --nologo
10+
11+
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS final
12+
13+
WORKDIR /app
14+
COPY --from=build /app/ .
15+
16+
ENTRYPOINT ["dotnet", "./OpenApi.Client.Mcp.dll"]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// This Source Code Form is subject to the terms of the MIT License.
2+
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3+
// Copyright (C) Leszek Pomianowski and OpenAPI Client Contributors.
4+
// All Rights Reserved.
5+
6+
global using Microsoft.AspNetCore.Builder;
7+
global using Microsoft.CodeAnalysis;
8+
global using Microsoft.Extensions.Configuration;
9+
global using Microsoft.Extensions.DependencyInjection;
10+
global using Microsoft.Extensions.Logging;
11+
global using OpenApi.Client.Mcp.Configuration;
12+
global using OpenApi.Client.Mcp.Services;
13+
global using OpenApi.Client.Mcp.Tools;
14+
global using OpenTelemetry;
15+
global using OpenTelemetry.Metrics;
16+
global using OpenTelemetry.Trace;
17+
global using System;
18+
global using System.Collections.Generic;
19+
global using System.ComponentModel;
20+
global using System.IO;
21+
global using System.Linq;
22+
global using System.Net.Http;
23+
global using System.Threading;
24+
global using System.Threading.Tasks;

src/OpenApi.Client.Mcp/Log.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// This Source Code Form is subject to the terms of the MIT License.
2+
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3+
// Copyright (C) Leszek Pomianowski and OpenAPI Client Contributors.
4+
// All Rights Reserved.
5+
6+
namespace OpenApi.Client.Mcp;
7+
8+
internal static partial class Log
9+
{
10+
private enum Event
11+
{
12+
FailedToFetchOpenApi = 042137,
13+
FatalErrorWhileFetchingOpenApi,
14+
}
15+
16+
[LoggerMessage(
17+
EventId = (int)Event.FailedToFetchOpenApi,
18+
EventName = nameof(Event.FailedToFetchOpenApi),
19+
Level = LogLevel.Warning,
20+
Message = "Failed to fetch OpenAPI document from \"{Url}\". Please check the URL and your network connection."
21+
)]
22+
public static partial void LogFetchingFailed(this ILogger logger, string url);
23+
24+
[LoggerMessage(
25+
EventId = (int)Event.FatalErrorWhileFetchingOpenApi,
26+
EventName = nameof(Event.FatalErrorWhileFetchingOpenApi),
27+
Level = LogLevel.Error,
28+
Message = "Fatal error while fetching OpenAPI document from \"{Url}\". Please check the URL and your network connection."
29+
)]
30+
public static partial void LogFetchingFailedWithError(
31+
this ILogger logger,
32+
Exception exception,
33+
string url
34+
);
35+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
<PropertyGroup>
3+
<TargetFramework>$(CommonTargetFramework)</TargetFramework>
4+
<UserSecretsId>47a6b027-b381-4465-8c68-1f63fc721408</UserSecretsId>
5+
<DockerfileContext>..\..</DockerfileContext>
6+
<ContainerDevelopmentMode>Fast</ContainerDevelopmentMode>
7+
<DockerfileRunEnvironmentFiles>..\..\.env</DockerfileRunEnvironmentFiles>
8+
</PropertyGroup>
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.CodeAnalysis.Common" />
11+
<PackageReference Include="ModelContextProtocol" />
12+
<PackageReference Include="ModelContextProtocol.AspNetCore" />
13+
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" />
14+
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
15+
<PackageReference Include="Microsoft.OpenApi" />
16+
<PackageReference Include="Microsoft.OpenApi.YamlReader" />
17+
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
18+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
19+
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
20+
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
21+
</ItemGroup>
22+
<ItemGroup>
23+
<ProjectReference Include="..\OpenApi.Client.SourceGenerators\OpenApi.Client.SourceGenerators.csproj" />
24+
</ItemGroup>
25+
</Project>

src/OpenApi.Client.Mcp/Program.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// This Source Code Form is subject to the terms of the MIT License.
2+
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3+
// Copyright (C) Leszek Pomianowski and OpenAPI Client Contributors.
4+
// All Rights Reserved.
5+
6+
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
7+
8+
builder.Logging.AddConsole(consoleLogOptions =>
9+
{
10+
// Configure all logs to go to stderr
11+
consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
12+
});
13+
14+
#if DEBUG
15+
builder
16+
.Services.AddOpenTelemetry()
17+
.WithTracing(b => b.AddSource("*").AddAspNetCoreInstrumentation().AddHttpClientInstrumentation())
18+
.WithMetrics(b => b.AddMeter("*").AddAspNetCoreInstrumentation().AddHttpClientInstrumentation())
19+
.WithLogging()
20+
.UseOtlpExporter();
21+
#endif
22+
23+
builder.Services.AddTransient<IOpenApiService, OpenApiService>();
24+
25+
builder.Services.AddHttpClient();
26+
27+
ServerOptions serverOptions = new();
28+
IConfigurationSection section = builder.Configuration.GetSection("Server");
29+
section.Bind(serverOptions);
30+
31+
string? mode = Environment.GetEnvironmentVariable("mode") ?? Environment.GetEnvironmentVariable("MODE");
32+
33+
if (mode?.Contains("both", StringComparison.InvariantCultureIgnoreCase) ?? false)
34+
{
35+
serverOptions.Mode = McpMode.Both;
36+
}
37+
else if (mode?.Contains("http", StringComparison.InvariantCultureIgnoreCase) ?? false)
38+
{
39+
serverOptions.Mode = McpMode.Http;
40+
}
41+
42+
IMcpServerBuilder mcpBuilder = builder.Services.AddMcpServer();
43+
44+
if (serverOptions.Mode == McpMode.Both)
45+
{
46+
_ = mcpBuilder.WithHttpTransport().WithStdioServerTransport();
47+
}
48+
else if (serverOptions.Mode == McpMode.Stdio)
49+
{
50+
_ = mcpBuilder.WithStdioServerTransport();
51+
}
52+
else
53+
{
54+
_ = mcpBuilder.WithHttpTransport();
55+
}
56+
57+
_ = mcpBuilder.WithTools<OpenApiTools>();
58+
59+
await using WebApplication app = builder.Build();
60+
61+
if (serverOptions.Mode is McpMode.Http or McpMode.Both)
62+
{
63+
app.MapMcp();
64+
}
65+
66+
await app.RunAsync();
67+
68+
return;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"profiles": {
3+
"ModelContextProtocol": {
4+
"commandName": "Project",
5+
"launchBrowser": false,
6+
"environmentVariables": {
7+
"ASPNETCORE_ENVIRONMENT": "Development",
8+
"OTEL_SERVICE_NAME": "sse-server",
9+
"MODE": "Both"
10+
},
11+
"applicationUrl": "http://localhost:64622"
12+
}
13+
}
14+
}

0 commit comments

Comments
 (0)