Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
55f284f
Add PackageVersion for Kusto.Data and Kusto.Ingest
conniey Oct 23, 2025
d1fc4a3
Add Id to shared McpModels.
conniey Oct 23, 2025
83ab661
Initial commit
conniey Oct 23, 2025
8c32772
Remove AoT
conniey Oct 23, 2025
16c13f7
Add initial mock up
conniey Oct 23, 2025
bd98516
Split to use Query and Ingestion endpoint.
conniey Oct 23, 2025
ecaeb8b
Add Query to create table.
conniey Oct 23, 2025
972b380
Add LaunchSettings.
conniey Oct 23, 2025
dc365e0
Set Default queries folder.
conniey Oct 23, 2025
e17d0b7
Adding Appsettings.json. Copy to output
conniey Oct 28, 2025
ae49d29
Update Json serialization to output Enum names.
conniey Oct 27, 2025
e95572e
Start host
conniey Oct 27, 2025
f0861ff
Move JsonOutput parsing. Fix command line arguments to pass to exe
conniey Oct 27, 2025
0d6c505
Pass in command rather than just name in changes.
conniey Oct 27, 2025
0699f0e
Update Logging
conniey Oct 27, 2025
d8fbc33
Change interface to use List. Update Ingestion settings for singlejson
conniey Oct 27, 2025
4c6acbf
Fix column mappings
conniey Oct 27, 2025
804d00d
Change to use Direct Ingestion client
conniey Oct 27, 2025
e5cc783
Fix formatting issues
conniey Oct 28, 2025
badc01f
Fix formatting
conniey Oct 28, 2025
3a335de
Add Friend assembly
conniey Oct 28, 2025
44d3999
Add unit test project
conniey Oct 28, 2025
01b56df
Make method virtual
conniey Oct 28, 2025
2af8847
Fix formatting issue.
conniey Oct 28, 2025
983fde8
Add header
conniey Oct 28, 2025
8d7bc67
Add dev settings
conniey Oct 28, 2025
5bcb034
Add header
conniey Oct 29, 2025
ff07ba3
Add ServerName support.
conniey Nov 11, 2025
d83833a
Remove test template.
conniey Nov 11, 2025
71d9ba6
Add ServerName
conniey Nov 11, 2025
597db74
Propagate cancellation
conniey Nov 11, 2025
fb79c79
Moving classes into src/tests
conniey Dec 4, 2025
8bb3106
Move parsing from command line to use IConfiguration.
conniey Jan 7, 2026
b2f78aa
Add ServerName column to Kusto mapping at the end.
conniey Jan 8, 2026
93fa81b
Add documentation to CommandLineOptions
conniey Jan 8, 2026
600b4c6
Add ServerInfoResult for `server info`
conniey Jan 8, 2026
3d1f865
Add IsDryRun, AzMcp to app configuration
conniey Jan 8, 2026
fc03c95
Update Program to configure AppConfiguration and CommandLineOptions
conniey Jan 8, 2026
214f08d
Add FileName parameter to utility methods.
conniey Jan 8, 2026
c357172
Update ToolAnalyzer to use file name parameter from Utility.
conniey Jan 8, 2026
13e10dc
Create functions for AzMcp program.
conniey Jan 12, 2026
176b01e
Change utility classes into a class
conniey Jan 12, 2026
5bf0592
- Add utility into DI container
conniey Jan 12, 2026
890df96
Use lazy initialization.
conniey Jan 12, 2026
5931cfa
Add tests
conniey Jan 12, 2026
9464f57
Make methods virtual for testability
conniey Jan 12, 2026
bac16c8
Add tests for AzaureMcpKustoDatastore
conniey Jan 12, 2026
7216f5f
Add ToolAnalyzerTests
conniey Jan 12, 2026
b47db2c
Fix formatting error in AzureMcpKustoDatastore
conniey Jan 13, 2026
b2a3274
Fix formatting error in ToolAnalyzerTests
conniey Jan 13, 2026
ff9d8e7
Fix whitespace AzmcpProgram
conniey Jan 13, 2026
c1af889
Add Ticks to FileName.
conniey Jan 13, 2026
e5676b2
Update eng/tools/ToolMetadataExporter/src/Utility.cs
conniey Jan 14, 2026
b312304
Remove extra line.
conniey Jan 14, 2026
d7280ae
Dispose process.
conniey Jan 14, 2026
d9f23fc
Remove unused dependency
conniey Jan 14, 2026
76c869c
Replace with constant.
conniey Jan 14, 2026
4f05d64
Changes to Debug.
conniey Jan 14, 2026
1584cff
Add Tool analysis date.
conniey Jan 14, 2026
f60ac82
Add README.
conniey Jan 14, 2026
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
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
<PackageVersion Include="Azure.ResourceManager.StorageSync" Version="1.3.1" />
<PackageVersion Include="Azure.ResourceManager.FileShares" Version="1.0.0-beta.1" />
<PackageVersion Include="Azure.ResourceManager.ResourceGraph" Version="1.1.0" />
<PackageVersion Include="Microsoft.Azure.Kusto.Data" Version="13.0.2" />
<PackageVersion Include="Microsoft.Azure.Kusto.Data" Version="14.0.1" />
<PackageVersion Include="Microsoft.Azure.Kusto.Ingest" Version="14.0.1" />
<PackageVersion Include="Microsoft.CognitiveServices.Speech" Version="1.46.0" />
<PackageVersion Include="Microsoft.Identity.Client.Broker" Version="4.72.1" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.2.0" />
Expand Down
3 changes: 3 additions & 0 deletions eng/tools/ToolDescriptionEvaluator/src/Models/McpModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ public class ToolAnnotations
// Tool definition for azmcp tools list response
public class Tool
{
[JsonPropertyName("id")]
public string? Id { get; set; }

[JsonPropertyName("name")]
public required string Name { get; set; }

Expand Down
25 changes: 25 additions & 0 deletions eng/tools/ToolMetadataExporter/src/AppConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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; }

public bool IsDryRun { get; set; }

public string? AzmcpExe { get; set; }

public bool IsAzmcpExeSpecified { get; set; }
}
7 changes: 7 additions & 0 deletions eng/tools/ToolMetadataExporter/src/AssemblyInfo.cs
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")]
5 changes: 5 additions & 0 deletions eng/tools/ToolMetadataExporter/src/GlobalUsings.cs
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;
9 changes: 9 additions & 0 deletions eng/tools/ToolMetadataExporter/src/Models/AzureMcpTool.cs
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);
26 changes: 26 additions & 0 deletions eng/tools/ToolMetadataExporter/src/Models/CommandLineOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace ToolMetadataExporter.Models;

/// <summary>
/// Options specified via command line arguments. Supported options are:
/// <list type="bullet">
/// <item><c>--dry-run</c>: If specified, the tool will run in dry-run mode, meaning no changes will be made to the target datastore.</item>
/// <item><c>--azmcp-exe &lt;path&gt;</c>: The path to the azmcp executable to use for interacting with the MCP server.</item>
/// </list>
/// </summary>
internal class CommandLineOptions
{
/// <summary>
/// Gets or sets a value indicating whether the tool analysis should be performed as a dry run.
/// </summary>
/// <remarks>When set to <see langword="true"/>, the operation is performed, output to the console, but not persisted to the datastore. When set to
/// <see langword="false"/>, the operation is executed normally.</remarks>
public bool? IsDryRun { get; set; }

/// <summary>
/// Gets or sets the full path to the AzMcp executable file.
/// </summary>
public string? AzmcpExe { get; set; }
}
62 changes: 62 additions & 0 deletions eng/tools/ToolMetadataExporter/src/Models/Kusto/McpToolEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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" },
new ColumnMapping { ColumnName = ServerNameColumn, 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,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.Json.Serialization;
using ToolMetadataExporter.Models.Kusto;

namespace ToolMetadataExporter.Models;

[JsonSerializable(typeof(ServerInfo))]
[JsonSerializable(typeof(ServerInfoResult))]
[JsonSerializable(typeof(McpToolEvent))]
[JsonSerializable(typeof(McpToolEventType))]
[JsonSerializable(typeof(List<McpToolEvent>))]
[JsonSourceGenerationOptions(Converters = [typeof(JsonStringEnumConverter<McpToolEventType>)])]
public partial class ModelsSerializationContext : JsonSerializerContext
{
}
30 changes: 30 additions & 0 deletions eng/tools/ToolMetadataExporter/src/Models/ServerInfoResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.Json.Serialization;

namespace ToolMetadataExporter.Models;

/// <summary>
/// The result of a `server info` request from the MCP server.
/// </summary>
public class ServerInfoResult
{
[JsonPropertyName("status")]
public int Status { get; set; }

[JsonPropertyName("message")]
public string? Message { get; set; }

[JsonPropertyName("results")]
public ServerInfo? Results { get; set; }
}

public class ServerInfo
{
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;

[JsonPropertyName("version")]
public string Version { get; set; } = string.Empty;
}
119 changes: 119 additions & 0 deletions eng/tools/ToolMetadataExporter/src/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// 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.Models;
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 analyzer.RunAsync(DateTimeOffset.UtcNow);
}

private static void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddLogging(builder =>
{
builder.AddConsole();
});

services.AddSingleton<IAzureMcpDatastore, AzureMcpKustoDatastore>()
.AddSingleton<Utility>()
.AddSingleton<AzmcpProgram>()
.AddSingleton<ToolAnalyzer>();

services.AddOptions<CommandLineOptions>()
.Bind(configuration);

services.AddOptions<AppConfiguration>()
.Bind<AppConfiguration>(configuration.GetSection("AppConfig"))
.Configure<IOptions<CommandLineOptions>>((existing, commandLineOptions) =>
{
// Command-line IsDryRun overrides appsettings.json file value.
if (commandLineOptions.Value.IsDryRun.HasValue)
{
existing.IsDryRun = commandLineOptions.Value.IsDryRun.Value;
}

var exeDir = AppContext.BaseDirectory;

// If a path to azmcp.exe is not provided. Assume that this is running within the context of
// the repository and try to find it.
existing.IsAzmcpExeSpecified = !string.IsNullOrEmpty(commandLineOptions.Value.AzmcpExe);
if (existing.IsAzmcpExeSpecified)
{
existing.AzmcpExe = commandLineOptions.Value.AzmcpExe!;

if (existing.WorkDirectory == null)
{
existing.WorkDirectory = exeDir;
}
}
else
{
var repoRoot = Utility.FindRepoRoot(exeDir);
if (existing.WorkDirectory == null)
{
existing.WorkDirectory = Path.Combine(repoRoot, ".work");
}

existing.AzmcpExe = Path.Combine(repoRoot, "eng", "tools", "Azmcp", "azmcp.exe");
}
});
}

private static void ConfigureAzureServices(IServiceCollection services)
{
services.AddScoped<TokenCredential>(sp =>
{
var credential = new ChainedTokenCredential(
new ManagedIdentityCredential(),
new DefaultAzureCredential()
);

return credential;
});
services.AddSingleton<ICslQueryProvider>(sp =>
{
var config = sp.GetRequiredService<IOptions<AppConfiguration>>();

var connectionStringBuilder = new KustoConnectionStringBuilder(config.Value.QueryEndpoint)
.WithAadUserPromptAuthentication()
.WithAadAzCliAuthentication(interactive: true);

return KustoClientFactory.CreateCslQueryProvider(connectionStringBuilder);
});
services.AddSingleton<IKustoIngestClient>(sp =>
{
var config = sp.GetRequiredService<IOptions<AppConfiguration>>();

var connectionStringBuilder = new KustoConnectionStringBuilder(config.Value.IngestionEndpoint)
.WithAadUserPromptAuthentication()
.WithAadAzCliAuthentication(interactive: true);

return KustoIngestFactory.CreateDirectIngestClient(connectionStringBuilder);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"profiles": {
"ToolMetadataExporter": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}
36 changes: 36 additions & 0 deletions eng/tools/ToolMetadataExporter/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Tool Metadata Exporter


## Development Setup

### 1. Create Datastore

1. Create an Azure Data Explorer Cluster (e.g. "mcp-test-instance")
1. Create a database (e.g. "McpToolMetadata") within the cluster
1. In https://dataexplorer.azure.com/, connect to cluster
1. Click on "Query" tab
1. Execute [`CreateTable.kql`](./Resources/queries/CreateTable.kql) against the cluster and database

### 2. Configure Application

1. Open [`appsettings.Development.json`](./appsettings.Development.json)
1. Update "IngestionEndpoint", "QueryEndpoint", "DatabaseName" with the appropriate cluster and database names. Using the example from previous step, it would look like this:
```json
"AppConfig": {
"IngestionEndpoint": "https://ingest-mcp-test-instance.westus2.kusto.windows.net",
"QueryEndpoint": "https://mcp-test-instance.westus2.kusto.windows.net",
"DatabaseName": "McpToolMetadata"
}
```
To find values for "QueryEndpoint" and "IngestionEndpoint", navigate to the Azure Data Explorer Cluster in the Azure portal, and in the "Essentials" panel window, look for.
1. "IngestionEndpoint" is "Data Ingestion URI"
1. "QueryEndpoint" is "URI"

### 3. Run Application

1. Open a terminal in the project src directory: [$RepositoryRoot/eng/tools/ToolMetadataExporter/src](./)
1. Run the application using the command:
```bash
dotnet run --environment DOTNET_ENVIRONMENT=Development
```
The environment variable `DOTNET_ENVIRONMENT` is set to `Development` to ensure the application uses [`appsettings.Development.json`](./appsettings.Development.json)
Loading
Loading