-
Notifications
You must be signed in to change notification settings - Fork 299
Add ToolMetadataExporter #992
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3dfd4f9
1342541
3cba80c
5c9fd41
4af70a6
4e3adf7
0bbad1a
fe07ce8
49e88c3
725dc59
c9ba1c1
7207b1a
b27c786
ebd8733
ec1e149
901cb27
238c33b
46e1d4f
5bb8bcc
08dd0f5
12fd8b5
2d0f10b
dc326ce
792c53e
e13fd7e
bdd8fb5
a454034
be54a7f
901c5a9
bf8aee9
d244e0e
bbded74
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <IsTestProject>true</IsTestProject> | ||
| <OutputType>Exe</OutputType> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <PackageReference Include="NSubstitute" /> | ||
| <PackageReference Include="NSubstitute.Analyzers.CSharp" /> | ||
| <PackageReference Include="xunit.v3" /> | ||
| <PackageReference Include="xunit.runner.visualstudio" /> | ||
| <PackageReference Include="coverlet.collector" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\ToolMetadataExporter\ToolMetadataExporter.csproj" /> | ||
| </ItemGroup> | ||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| namespace ToolMetadataExporter; | ||
|
|
||
| public class AppConfiguration | ||
| { | ||
| public string? IngestionEndpoint { get; set; } | ||
|
|
||
| public string? QueryEndpoint { get; set; } | ||
|
|
||
| public string? DatabaseName { get; set; } | ||
|
|
||
| public string? McpToolEventsTableName { get; set; } | ||
|
|
||
| public string? QueriesFolder { get; set; } = "Resources/queries"; | ||
|
|
||
| public string? WorkDirectory { get; set; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Runtime.CompilerServices; | ||
|
|
||
| [assembly: InternalsVisibleTo("ToolMetadataExporter.UnitTests")] | ||
| [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| global using System; | ||
| global using System.Text.Json; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| namespace ToolMetadataExporter.Models; | ||
|
|
||
| public record AzureMcpTool( | ||
| string ToolId, | ||
| string ToolName, | ||
| string ToolArea); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Text.Json.Serialization; | ||
| using Kusto.Data.Common; | ||
|
|
||
| namespace ToolMetadataExporter.Models.Kusto; | ||
|
|
||
| public class McpToolEvent | ||
| { | ||
| private const string EventTimeColumn = "EventTime"; | ||
| private const string EventTypeColumn = "EventType"; | ||
| private const string ServerNameColumn = "ServerName"; | ||
| private const string ServerVersionColumn = "ServerVersion"; | ||
| private const string ToolIdColumn = "ToolId"; | ||
| private const string ToolNameColumn = "ToolName"; | ||
| private const string ToolAreaColumn = "ToolArea"; | ||
| private const string ReplacedByToolNameColumn = "ReplacedByToolName"; | ||
| private const string ReplacedByToolAreaColumn = "ReplacedByToolArea"; | ||
|
|
||
| [JsonPropertyName(EventTimeColumn)] | ||
| public DateTimeOffset? EventTime { get; set; } | ||
|
|
||
| [JsonPropertyName(EventTypeColumn)] | ||
| public McpToolEventType? EventType { get; set; } | ||
|
|
||
| [JsonPropertyName(ServerNameColumn)] | ||
| public string? ServerName { get; set; } | ||
|
|
||
| [JsonPropertyName(ServerVersionColumn)] | ||
| public string? ServerVersion { get; set; } | ||
|
|
||
| [JsonPropertyName(ToolIdColumn)] | ||
| public string? ToolId { get; set; } | ||
|
|
||
| [JsonPropertyName(ToolNameColumn)] | ||
| public string? ToolName { get; set; } | ||
|
|
||
| [JsonPropertyName(ToolAreaColumn)] | ||
| public string? ToolArea { get; set; } | ||
|
|
||
| [JsonPropertyName(ReplacedByToolNameColumn)] | ||
| public string? ReplacedByToolName { get; set; } | ||
|
|
||
| [JsonPropertyName(ReplacedByToolAreaColumn)] | ||
| public string? ReplacedByToolArea { get; set; } | ||
|
|
||
| public static ColumnMapping[] GetColumnMappings() | ||
| { | ||
| return [ | ||
| new ColumnMapping { ColumnName = EventTimeColumn, ColumnType = "datetime" }, | ||
| new ColumnMapping { ColumnName = EventTypeColumn, ColumnType = "string"}, | ||
| new ColumnMapping { ColumnName = ReplacedByToolAreaColumn, ColumnType = "string"}, | ||
| new ColumnMapping { ColumnName = ReplacedByToolNameColumn, ColumnType = "string"}, | ||
| new ColumnMapping { ColumnName = ServerVersionColumn, ColumnType = "string" }, | ||
| new ColumnMapping { ColumnName = ToolAreaColumn , ColumnType = "string" }, | ||
| new ColumnMapping { ColumnName = ToolIdColumn, ColumnType = "string"}, | ||
| new ColumnMapping { ColumnName = ToolNameColumn, ColumnType = "string" }, | ||
| ]; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace ToolMetadataExporter.Models.Kusto; | ||
|
|
||
| public enum McpToolEventType | ||
| { | ||
| [JsonStringEnumMemberName("Created")] | ||
| Created, | ||
| [JsonStringEnumMemberName("Updated")] | ||
| Updated, | ||
| [JsonStringEnumMemberName("Deleted")] | ||
| Deleted | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Text.Json.Serialization; | ||
| using ToolMetadataExporter.Models.Kusto; | ||
|
|
||
| namespace ToolMetadataExporter.Models; | ||
|
|
||
| [JsonSerializable(typeof(McpToolEvent))] | ||
| [JsonSerializable(typeof(McpToolEventType))] | ||
| [JsonSerializable(typeof(List<McpToolEvent>))] | ||
| [JsonSourceGenerationOptions(Converters = [typeof(JsonStringEnumConverter<McpToolEventType>)])] | ||
| public partial class ModelsSerializationContext : JsonSerializerContext | ||
| { | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,99 @@ | ||||||||||||||||||||||||||||||||||||||
| // Copyright (c) Microsoft Corporation. | ||||||||||||||||||||||||||||||||||||||
| // Licensed under the MIT License. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| using Azure.Core; | ||||||||||||||||||||||||||||||||||||||
| using Azure.Identity; | ||||||||||||||||||||||||||||||||||||||
| using Kusto.Data; | ||||||||||||||||||||||||||||||||||||||
| using Kusto.Data.Common; | ||||||||||||||||||||||||||||||||||||||
| using Kusto.Data.Net.Client; | ||||||||||||||||||||||||||||||||||||||
| using Kusto.Ingest; | ||||||||||||||||||||||||||||||||||||||
| using Microsoft.Extensions.Configuration; | ||||||||||||||||||||||||||||||||||||||
| using Microsoft.Extensions.DependencyInjection; | ||||||||||||||||||||||||||||||||||||||
| using Microsoft.Extensions.Hosting; | ||||||||||||||||||||||||||||||||||||||
| using Microsoft.Extensions.Logging; | ||||||||||||||||||||||||||||||||||||||
| using Microsoft.Extensions.Options; | ||||||||||||||||||||||||||||||||||||||
| using ToolMetadataExporter.Services; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| namespace ToolMetadataExporter; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| public class Program | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| public static async Task Main(string[] args) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| var builder = Host.CreateApplicationBuilder(args); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| ConfigureServices(builder.Services, builder.Configuration); | ||||||||||||||||||||||||||||||||||||||
| ConfigureAzureServices(builder.Services); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| var host = builder.Build(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| var analyzer = host.Services.GetRequiredService<ToolAnalyzer>(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| await host.StartAsync(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| var isDryRunValue = builder.Configuration["IsDryRun"]; | ||||||||||||||||||||||||||||||||||||||
| var isDryRun = false; | ||||||||||||||||||||||||||||||||||||||
| if (bool.TryParse(isDryRunValue, out var parsed)) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| isDryRun = parsed; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| await analyzer.RunAsync(DateTimeOffset.UtcNow, isDryRun); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| private static void ConfigureServices(IServiceCollection services, IConfiguration configuration) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| services.AddLogging(builder => | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| builder.AddConsole(); | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| services.AddSingleton<IAzureMcpDatastore, AzureMcpKustoDatastore>() | ||||||||||||||||||||||||||||||||||||||
| .AddSingleton<AzmcpProgram>() | ||||||||||||||||||||||||||||||||||||||
| .AddSingleton<ToolAnalyzer>(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| services.Configure<AppConfiguration>(configuration.GetSection("AppConfig")) | ||||||||||||||||||||||||||||||||||||||
| .PostConfigure<AppConfiguration>(existing => | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| if (existing.WorkDirectory == null) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| string exeDir = AppContext.BaseDirectory; | ||||||||||||||||||||||||||||||||||||||
| var repoRoot = Utility.FindRepoRoot(exeDir); | ||||||||||||||||||||||||||||||||||||||
| existing.WorkDirectory = Path.Combine(repoRoot, ".work"); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+58
to
+63
|
||||||||||||||||||||||||||||||||||||||
| if (existing.WorkDirectory == null) | |
| { | |
| string exeDir = AppContext.BaseDirectory; | |
| var repoRoot = Utility.FindRepoRoot(exeDir); | |
| existing.WorkDirectory = Path.Combine(repoRoot, ".work"); | |
| } | |
| string exeDir = AppContext.BaseDirectory; | |
| var repoRoot = Utility.FindRepoRoot(exeDir); | |
| if (existing.WorkDirectory == null) | |
| { | |
| existing.WorkDirectory = Path.Combine(repoRoot, ".work"); | |
| } | |
| if (string.IsNullOrEmpty(existing.QueriesFolder) || !Path.IsPathRooted(existing.QueriesFolder)) | |
| { | |
| existing.QueriesFolder = Path.Combine(repoRoot, existing.QueriesFolder ?? "Resources/queries"); | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "profiles": { | ||
| "ToolMetadataExporter": { | ||
| "commandName": "Project", | ||
| "environmentVariables": { | ||
| "DOTNET_ENVIRONMENT": "Development" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| .create table McpToolEvents ( | ||
| EventTime: datetime, | ||
| EventType: string, | ||
| ServerVersion: string, | ||
| ToolId: string, | ||
| ToolName: string, | ||
| ToolArea: string, | ||
| ReplacedByToolName: string, | ||
| ReplacedByToolArea: string | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| McpToolEvents | ||
| | summarize arg_max(EventTime, *) by ToolId | ||
| | where EventType != 'Deleted' | ||
| | project EventTime, ToolId, ToolName, ToolArea, ServerVersion, EventType, ReplacedByToolName, ReplacedByToolArea |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| using Microsoft.Extensions.Options; | ||
|
||
| using ToolSelection.Models; | ||
|
|
||
| namespace ToolMetadataExporter.Services; | ||
|
|
||
| public class AzmcpProgram | ||
| { | ||
| private readonly string _toolDirectory; | ||
|
|
||
| public Task<string> GetServerNameAsync() | ||
| { | ||
| return Utility.GetServerName(); | ||
| } | ||
|
|
||
| public AzmcpProgram(IOptions<AppConfiguration> options) | ||
| { | ||
| _toolDirectory = options.Value.WorkDirectory ?? throw new ArgumentNullException(nameof(AppConfiguration.WorkDirectory)); | ||
| } | ||
|
|
||
| public virtual Task<ListToolsResult?> LoadToolsDynamicallyAsync() | ||
| { | ||
| return Utility.LoadToolsDynamicallyAsync(_toolDirectory, false); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are currently utility methods but the logic is from ToolDescriptionEvaluator where I think the logic can be shared. |
||
| } | ||
|
|
||
| public virtual Task<string> GetVersionAsync() | ||
| { | ||
| return Utility.GetVersionAsync(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing copyright header. According to the coding standards, all C# files should include the copyright header:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
????????????