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
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PackageVersion Include="Azure.Identity" Version="1.16.0" />
<PackageVersion Include="Azure.Developer.LoadTesting" Version="1.0.2" />
<PackageVersion Include="Azure.Identity.Broker" Version="1.3.0" />
<PackageVersion Include="Azure.Messaging.EventGrid" Version="4.24.0" />
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.20.1" />
<PackageVersion Include="Azure.Monitor.Ingestion" Version="1.2.0" />
<PackageVersion Include="Azure.Monitor.Query" Version="1.7.1" />
Expand Down
7 changes: 7 additions & 0 deletions docs/azmcp-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,13 @@ azmcp eventgrid subscription list --subscription <subscription> \
[--resource-group <resource-group>] \
[--topic <topic>]
[--location <location>]

# Publish custom events to Event Grid topics
azmcp eventgrid events publish --subscription <subscription> \
--topic <topic> \
--data <json-event-data> \
[--resource-group <resource-group>] \
[--schema <schema-type>]
```

### Azure Function App Operations
Expand Down
3 changes: 3 additions & 0 deletions docs/e2eTestPrompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ This file contains prompts used for end-to-end testing to ensure each tool is in
| azmcp_eventgrid_subscription_list | List all Event Grid subscriptions in subscription <subscription> |
| azmcp_eventgrid_subscription_list | Show Event Grid subscriptions in resource group <resource_group_name> in subscription <subscription> |
| azmcp_eventgrid_subscription_list | List Event Grid subscriptions for subscription <subscription> in location <location> |
| azmcp_eventgrid_events_publish | Publish an event to Event Grid topic <topic_name> using <event_schema> with the following data <event_data> |
| azmcp_eventgrid_events_publish | Publish event to my Event Grid topic <topic_name> with the following events <event_data> |
| azmcp_eventgrid_events_publish | Send an event to Event Grid topic <topic_name> in resource group <resource_group_name> with <event_data> |

## Azure Function App

Expand Down
4 changes: 4 additions & 0 deletions servers/Azure.Mcp.Server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ The Azure MCP Server updates automatically by default whenever a new release com

## 0.8.2 (2025-09-25)

### Features Added

-- Added support for publishing custom events to Event Grid topics via the command `azmcp_eventgrid_events_publish`. Supports EventGrid, CloudEvents, and custom schemas with structured event data delivery for event-driven architectures. [[#514](https://github.com/microsoft/mcp/pull/514)]

### Bugs Fixed

- Fixed `azmcp_subscription_list` to return empty enumerable instead of `null` when no subscriptions are found. [[#508](https://github.com/microsoft/mcp/pull/508)]
Expand Down
2 changes: 2 additions & 0 deletions servers/Azure.Mcp.Server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ The Azure MCP Server supercharges your agents with Azure context. Here are some
* "List Event Grid subscriptions for topic 'my-topic' in subscription 'my-subscription'"
* "List Event Grid Subscriptions in subscription 'my-subscription'"
* "List Event Grid subscriptions for topic 'my-topic' in location 'my-location'"
* "Publish an event with data '{\"name\": \"test\"}' to topic 'my-topic' using CloudEvents schema"
* "Send custom event data to Event Grid topic 'analytics-events' with EventGrid schema"

### 🔑 Azure Key Vault

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<ProjectReference Include="..\..\..\core\Azure.Mcp.Core\src\Azure.Mcp.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Messaging.EventGrid" />
<PackageReference Include="Azure.ResourceManager" />
<PackageReference Include="Azure.ResourceManager.EventGrid" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@
// Licensed under the MIT License.

using System.Text.Json.Serialization;
using Azure.Mcp.Tools.EventGrid.Commands.Events;
using Azure.Mcp.Tools.EventGrid.Commands.Subscription;
using Azure.Mcp.Tools.EventGrid.Commands.Topic;
using Azure.Mcp.Tools.EventGrid.Models;

namespace Azure.Mcp.Tools.EventGrid.Commands;

// JsonSerializable attributes for all types used in EventGrid command responses and event serialization
[JsonSerializable(typeof(EventGridPublishCommand.EventGridPublishCommandResult))]
[JsonSerializable(typeof(SubscriptionListCommand.SubscriptionListCommandResult))]
[JsonSerializable(typeof(TopicListCommand.TopicListCommandResult))]
[JsonSerializable(typeof(EventGridSubscriptionInfo))]
[JsonSerializable(typeof(EventGridTopicInfo))]
[JsonSerializable(typeof(EventPublishResult))]
[JsonSerializable(typeof(EventGridEventSchema))] // For individual event serialization to EventGrid
[JsonSerializable(typeof(CloudEvent))] // For CloudEvent schema input deserialization
[JsonSerializable(typeof(EventGridEventInput))] // For EventGrid schema input deserialization
[JsonSerializable(typeof(CustomEvent))] // For custom event schema input deserialization
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
internal sealed partial class EventGridJsonContext : JsonSerializerContext
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.CommandLine;
using System.CommandLine.Parsing;
using System.Net;
using Azure.Mcp.Core.Extensions;
using Azure.Mcp.Core.Models.Option;
using Azure.Mcp.Tools.EventGrid.Options;
using Azure.Mcp.Tools.EventGrid.Options.Events;
using Azure.Mcp.Tools.EventGrid.Services;

namespace Azure.Mcp.Tools.EventGrid.Commands.Events;

public sealed class EventGridPublishCommand(ILogger<EventGridPublishCommand> logger) : BaseEventGridCommand<EventsPublishOptions>
{
private const string CommandTitle = "Publish Events to Event Grid Topic";
private readonly ILogger<EventGridPublishCommand> _logger = logger;

public override string Name => "publish";

public override string Description =>
"""
Publish custom events to Event Grid topics for event-driven architectures. This tool sends structured event data to
Event Grid topics with schema validation and delivery guarantees for downstream subscribers. Returns publish operation
status. Requires topic, data, and optional schema.
""";

public override string Title => CommandTitle;

public override ToolMetadata Metadata => new()
{
Destructive = false,
Idempotent = false,
OpenWorld = false,
ReadOnly = false,
LocalRequired = false,
Secret = false
};

protected override void RegisterOptions(Command command)
{
base.RegisterOptions(command);
command.Options.Add(OptionDefinitions.Common.ResourceGroup);
command.Options.Add(EventGridOptionDefinitions.TopicName);
command.Options.Add(EventGridOptionDefinitions.EventData);
command.Options.Add(EventGridOptionDefinitions.EventSchema);
}

protected override EventsPublishOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
options.ResourceGroup ??= parseResult.GetValueOrDefault<string>(OptionDefinitions.Common.ResourceGroup.Name);
options.TopicName = parseResult.GetValueOrDefault<string>(EventGridOptionDefinitions.TopicName.Name);
options.EventData = parseResult.GetValueOrDefault<string>(EventGridOptionDefinitions.EventData.Name);
options.EventSchema = parseResult.GetValueOrDefault<string>(EventGridOptionDefinitions.EventSchema.Name);
return options;
}

public override ValidationResult Validate(CommandResult commandResult, CommandResponse? commandResponse = null)
{
var result = base.Validate(commandResult, commandResponse);
if (!result.IsValid)
{
return result;
}

var eventSchema = commandResult.GetValueOrDefault(EventGridOptionDefinitions.EventSchema);
if (!string.IsNullOrEmpty(eventSchema))
{
var normalizedSchema = eventSchema.Trim().ToLowerInvariant().Replace(" ", "");
var supportedSchemas = new[] { "cloudevents", "eventgrid", "custom" };

if (!supportedSchemas.Contains(normalizedSchema))
{
result.IsValid = false;
result.ErrorMessage = "Invalid event schema specified. Supported schemas are: CloudEvents, EventGrid, or Custom.";

if (commandResponse != null)
{
commandResponse.Status = HttpStatusCode.BadRequest;
commandResponse.Message = result.ErrorMessage;
}
}
}

return result;
}

public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult)
{
if (!Validate(parseResult.CommandResult, context.Response).IsValid)
{
return context.Response;
}

var options = BindOptions(parseResult);

try
{
var eventGridService = context.GetService<IEventGridService>();
var result = await eventGridService.PublishEventAsync(
options.Subscription!,
options.ResourceGroup,
options.TopicName!,
options.EventData!,
options.EventSchema,
options.Tenant,
options.RetryPolicy);

context.Response.Results = ResponseResult.Create(
new(result),
EventGridJsonContext.Default.EventGridPublishCommandResult);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error publishing events to Event Grid topic. Subscription: {Subscription}, Topic: {TopicName}, Options: {@Options}",
options.Subscription, options.TopicName, options);
HandleException(context, ex);
}

return context.Response;
}

protected override string GetErrorMessage(Exception ex) => ex switch
{
Azure.RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.NotFound =>
"Event Grid topic not found. Please verify the topic name and resource group exist.",
Azure.RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.Forbidden =>
"Access denied to Event Grid topic. Please verify you have Event Grid Data Sender permissions.",
Azure.RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.BadRequest =>
"Invalid event data or schema format. Please verify the event data is valid JSON and matches the expected schema.",
System.Text.Json.JsonException jsonEx =>
$"Invalid JSON format in event data: {jsonEx.Message}",
_ => base.GetErrorMessage(ex)
};

protected override HttpStatusCode GetStatusCode(Exception ex) => ex switch
{
System.Text.Json.JsonException => HttpStatusCode.BadRequest,
_ => base.GetStatusCode(ex)
};

internal record EventGridPublishCommandResult(EventPublishResult Result);
}
17 changes: 14 additions & 3 deletions tools/Azure.Mcp.Tools.EventGrid/src/EventGridSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using Azure.Mcp.Core.Areas;
using Azure.Mcp.Tools.EventGrid.Commands.Events;
using Azure.Mcp.Tools.EventGrid.Commands.Subscription;
using Azure.Mcp.Tools.EventGrid.Commands.Topic;
using Azure.Mcp.Tools.EventGrid.Services;
Expand All @@ -18,13 +19,18 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton<IEventGridService, EventGridService>();
services.AddSingleton<TopicListCommand>();
services.AddSingleton<SubscriptionListCommand>();
services.AddSingleton<EventGridPublishCommand>();
}

public CommandGroup RegisterCommands(IServiceProvider serviceProvider)
{
// Event Grid top-level group
var eventGrid = new CommandGroup(Name, "Event Grid operations - Commands for managing and accessing Event Grid topics, domains, and event subscriptions.");

// Events subgroup
var events = new CommandGroup("events", "Event Grid event operations - Commands for publishing and managing events sent to Event Grid topics.");
eventGrid.AddSubGroup(events);

// Topics subgroup
var topics = new CommandGroup("topic", "Event Grid topic operations - Commands for managing Event Grid topics and their configurations.");
eventGrid.AddSubGroup(topics);
Expand All @@ -33,12 +39,17 @@ public CommandGroup RegisterCommands(IServiceProvider serviceProvider)
var subscriptions = new CommandGroup("subscription", "Event Grid subscription operations - Commands for managing event subscriptions with filtering and endpoint configuration.");
eventGrid.AddSubGroup(subscriptions);

// Register Events commands
var eventsPublish = serviceProvider.GetRequiredService<EventGridPublishCommand>();
events.AddCommand(eventsPublish.Name, eventsPublish);

// Register Topic commands
var listCommand = serviceProvider.GetRequiredService<TopicListCommand>();
topics.AddCommand(listCommand.Name, listCommand);
var topicList = serviceProvider.GetRequiredService<TopicListCommand>();
topics.AddCommand(topicList.Name, topicList);

// Register Subscription commands
subscriptions.AddCommand("list", serviceProvider.GetRequiredService<SubscriptionListCommand>());
var subscriptionList = serviceProvider.GetRequiredService<SubscriptionListCommand>();
subscriptions.AddCommand(subscriptionList.Name, subscriptionList);

return eventGrid;
}
Expand Down
1 change: 1 addition & 0 deletions tools/Azure.Mcp.Tools.EventGrid/src/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
global using Azure.Mcp.Core.Services.Azure.Subscription;
global using Azure.Mcp.Core.Services.Azure.Tenant;
global using Azure.Mcp.Tools.EventGrid.Models;
global using Azure.Messaging.EventGrid;
global using Microsoft.Extensions.Logging;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace Azure.Mcp.Tools.EventGrid.Models;

/// <summary>
/// Represents an event conforming to the EventGrid event schema for HTTP API publishing.
/// </summary>
public sealed class EventGridEventSchema
{
[JsonPropertyName("id")]
public required string Id { get; set; }

[JsonPropertyName("subject")]
public required string Subject { get; set; }

[JsonPropertyName("eventType")]
public required string EventType { get; set; }

[JsonPropertyName("dataVersion")]
public required string DataVersion { get; set; }

[JsonPropertyName("data")]
public JsonNode? Data { get; set; }

[JsonPropertyName("eventTime")]
public DateTimeOffset EventTime { get; set; }
}
Loading