Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.7.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="Microsoft.OpenApi.YamlReader" Version="2.0.0" />
<PackageVersion Include="Microsoft.OpenApi" Version="2.0.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" />
<PackageVersion Include="ModelContextProtocol" Version="0.3.0-preview.3" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.3.0-preview.3" />
<PackageVersion Include="NSubstitute" Version="5.1.0" />
<PackageVersion Include="PolySharp" Version="1.15.0" />
<PackageVersion Include="SharpYaml" Version="2.1.3" />
Expand All @@ -25,6 +29,12 @@
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.7" />
<PackageVersion Include="System.Text.Json" Version="9.0.7" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="OpenTelemetry" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Exporter.InMemory" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.3" />
<PackageVersion Include="xunit" Version="2.9.3" />
</ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions OpenApi.Client.sln
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApi.Client.SourceGenera
EndProject
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApi.Client.Mcp", "src\OpenApi.Client.Mcp\OpenApi.Client.Mcp.csproj", "{BA946406-F267-4996-8769-E5B607E18CB4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -53,6 +55,10 @@ Global
{0C407F0F-BE50-4850-B695-FE1C0910E8BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C407F0F-BE50-4850-B695-FE1C0910E8BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C407F0F-BE50-4850-B695-FE1C0910E8BB}.Release|Any CPU.Build.0 = Release|Any CPU
{BA946406-F267-4996-8769-E5B607E18CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA946406-F267-4996-8769-E5B607E18CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA946406-F267-4996-8769-E5B607E18CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA946406-F267-4996-8769-E5B607E18CB4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
13 changes: 13 additions & 0 deletions src/OpenApi.Client.Mcp/Configuration/McpMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and OpenAPI Client Contributors.
// All Rights Reserved.

namespace OpenApi.Client.Mcp.Configuration;

internal enum McpMode
{
Stdio,
Http,
Both,
}
11 changes: 11 additions & 0 deletions src/OpenApi.Client.Mcp/Configuration/ServerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and OpenAPI Client Contributors.
// All Rights Reserved.

namespace OpenApi.Client.Mcp.Configuration;

internal sealed class ServerOptions
{
public McpMode Mode { get; set; } = McpMode.Stdio;
}
16 changes: 16 additions & 0 deletions src/OpenApi.Client.Mcp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build

WORKDIR /src
COPY . .

RUN find . -type f -name 'appsettings.*.json' ! -name 'appsettings.json' -exec rm -f {} +
RUN find . -name 'launchSettings.json' -exec rm -f {} +

RUN dotnet publish "src/OpenApi.Client.Mcp/OpenApi.Client.Mcp.csproj" -c Release -o /app/ --nologo

FROM mcr.microsoft.com/dotnet/runtime:9.0 AS final

WORKDIR /app
COPY --from=build /app/ .

ENTRYPOINT ["dotnet", "./OpenApi.Client.Mcp.dll"]
24 changes: 24 additions & 0 deletions src/OpenApi.Client.Mcp/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and OpenAPI Client Contributors.
// All Rights Reserved.

global using Microsoft.AspNetCore.Builder;
global using Microsoft.CodeAnalysis;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Logging;
global using OpenApi.Client.Mcp.Configuration;
global using OpenApi.Client.Mcp.Services;
global using OpenApi.Client.Mcp.Tools;
global using OpenTelemetry;
global using OpenTelemetry.Metrics;
global using OpenTelemetry.Trace;
global using System;
global using System.Collections.Generic;
global using System.ComponentModel;
global using System.IO;
global using System.Linq;
global using System.Net.Http;
global using System.Threading;
global using System.Threading.Tasks;
35 changes: 35 additions & 0 deletions src/OpenApi.Client.Mcp/Log.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and OpenAPI Client Contributors.
// All Rights Reserved.

namespace OpenApi.Client.Mcp;

internal static partial class Log
{
private enum Event
{
FailedToFetchOpenApi = 042137,
FatalErrorWhileFetchingOpenApi,
}

[LoggerMessage(
EventId = (int)Event.FailedToFetchOpenApi,
EventName = nameof(Event.FailedToFetchOpenApi),
Level = LogLevel.Warning,
Message = "Failed to fetch OpenAPI document from \"{Url}\". Please check the URL and your network connection."
)]
public static partial void LogFetchingFailed(this ILogger logger, string url);

[LoggerMessage(
EventId = (int)Event.FatalErrorWhileFetchingOpenApi,
EventName = nameof(Event.FatalErrorWhileFetchingOpenApi),
Level = LogLevel.Error,
Message = "Fatal error while fetching OpenAPI document from \"{Url}\". Please check the URL and your network connection."
)]
public static partial void LogFetchingFailedWithError(
this ILogger logger,
Exception exception,
string url
);
}
25 changes: 25 additions & 0 deletions src/OpenApi.Client.Mcp/OpenApi.Client.Mcp.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>$(CommonTargetFramework)</TargetFramework>
<UserSecretsId>47a6b027-b381-4465-8c68-1f63fc721408</UserSecretsId>
<DockerfileContext>..\..</DockerfileContext>
<ContainerDevelopmentMode>Fast</ContainerDevelopmentMode>
<DockerfileRunEnvironmentFiles>..\..\.env</DockerfileRunEnvironmentFiles>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Common" />
<PackageReference Include="ModelContextProtocol" />
<PackageReference Include="ModelContextProtocol.AspNetCore" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="Microsoft.OpenApi" />
<PackageReference Include="Microsoft.OpenApi.YamlReader" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenApi.Client.SourceGenerators\OpenApi.Client.SourceGenerators.csproj" />
</ItemGroup>
</Project>
68 changes: 68 additions & 0 deletions src/OpenApi.Client.Mcp/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and OpenAPI Client Contributors.
// All Rights Reserved.

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Logging.AddConsole(consoleLogOptions =>
{
// Configure all logs to go to stderr
consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});

#if DEBUG
builder
.Services.AddOpenTelemetry()
.WithTracing(b => b.AddSource("*").AddAspNetCoreInstrumentation().AddHttpClientInstrumentation())
.WithMetrics(b => b.AddMeter("*").AddAspNetCoreInstrumentation().AddHttpClientInstrumentation())
.WithLogging()
.UseOtlpExporter();
#endif

builder.Services.AddTransient<IOpenApiService, OpenApiService>();

builder.Services.AddHttpClient();

ServerOptions serverOptions = new();
IConfigurationSection section = builder.Configuration.GetSection("Server");
section.Bind(serverOptions);

string? mode = Environment.GetEnvironmentVariable("mode") ?? Environment.GetEnvironmentVariable("MODE");

if (mode?.Contains("both", StringComparison.InvariantCultureIgnoreCase) ?? false)
{
serverOptions.Mode = McpMode.Both;
}
else if (mode?.Contains("http", StringComparison.InvariantCultureIgnoreCase) ?? false)
{
serverOptions.Mode = McpMode.Http;
}

IMcpServerBuilder mcpBuilder = builder.Services.AddMcpServer();

if (serverOptions.Mode == McpMode.Both)
{
_ = mcpBuilder.WithHttpTransport().WithStdioServerTransport();
}
else if (serverOptions.Mode == McpMode.Stdio)
{
_ = mcpBuilder.WithStdioServerTransport();
}
else
{
_ = mcpBuilder.WithHttpTransport();
}

_ = mcpBuilder.WithTools<OpenApiTools>();

await using WebApplication app = builder.Build();

if (serverOptions.Mode is McpMode.Http or McpMode.Both)
{
app.MapMcp();
}

await app.RunAsync();

return;
14 changes: 14 additions & 0 deletions src/OpenApi.Client.Mcp/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"profiles": {
"ModelContextProtocol": {
"commandName": "Project",
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"OTEL_SERVICE_NAME": "sse-server",
"MODE": "Both"
},
"applicationUrl": "http://localhost:64622"
}
}
}
34 changes: 34 additions & 0 deletions src/OpenApi.Client.Mcp/Services/IOpenApiService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and OpenAPI Client Contributors.
// All Rights Reserved.

namespace OpenApi.Client.Mcp.Services;

internal interface IOpenApiService
{
/// <summary>
/// Creates an OpenAPI client from the specified URL address of the OpenAPI JSON file or Swagger-generated JSON.
/// </summary>
Task<string> CreateFromFileAsync(string address, CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves a list of operations from the specified OpenAPI JSON file or Swagger-generated JSON.
/// </summary>
Task<string> GetOperationsAsync(string address, CancellationToken cancellationToken = default);

/// <summary>
/// Generates a curl command for a given operation ID from the OpenAPI specification.
/// </summary>
Task<string> GenerateCurlCommandAsync(
string address,
string operationId,
string? baseAddress,
CancellationToken cancellationToken = default
);

/// <summary>
/// Validates the OpenAPI document at the specified address and returns a string indicating the validation result.
/// </summary>
Task<string> ValidateDocumentAsync(string address, CancellationToken cancellationToken = default);
}
Loading
Loading