Skip to content
Draft
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
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ azd down

## Source Code

The function code for the `GetSnippet` and `SaveSnippet` endpoints are defined in [`SnippetsTool.cs`](./src/SnippetsTool.cs). The `McpToolsTrigger` attribute applied to the async `Run` method exposes the code function as an MCP Server.
The function code for the MCP tools are defined in [`SnippetsTool.cs`](./src/SnippetsTool.cs) and [`HelloTool.cs`](./src/HelloTool.cs). The `McpToolsTrigger` attribute applied to the function methods exposes the code function as an MCP Server.

This shows the code for a few MCP server examples (get string, get object, save object):

Expand Down Expand Up @@ -223,6 +223,70 @@ public string SaveSnippet(
}
```

### Complex Types Support

The MCP Tools extension supports complex data types including arrays, objects, numbers, and booleans. Here are examples from [`SnippetsTool.cs`](./src/SnippetsTool.cs) that demonstrate using complex types with `McpToolProperty`:

```csharp
[Function(nameof(BulkSaveSnippets))]
public string BulkSaveSnippets(
[McpToolTrigger("bulk_save_snippets", "Save multiple code snippets at once")] ToolInvocationContext context,
[McpToolProperty("snippets", ArrayPropertyType, "Array of snippet objects containing name, content, description, and tags")]
List<SnippetInfo> snippets,
[McpToolProperty("overwrite-existing", BooleanPropertyType, "Whether to overwrite existing snippets with same names")]
bool overwriteExisting = false
)
{
logger.LogInformation("Bulk saving {Count} snippets", snippets.Count);

var results = new List<object>();
foreach (var snippet in snippets)
{
// Process each snippet in the array
results.Add(new
{
Name = snippet.Name,
Status = "Success",
Message = $"Snippet '{snippet.Name}' saved successfully"
});
}

return JsonSerializer.Serialize(results, new JsonSerializerOptions { WriteIndented = true });
}

[Function(nameof(SearchSnippets))]
public string SearchSnippets(
[McpToolTrigger("search_snippets", "Search for snippets using various criteria")] ToolInvocationContext context,
[McpToolProperty("search-criteria", ObjectPropertyType, "Search criteria object with tags, name pattern, and content inclusion options")]
SnippetSearchCriteria searchCriteria
)
{
// Process complex object with multiple properties
var filteredResults = mockResults.AsEnumerable();

if (searchCriteria.Tags.Any())
{
filteredResults = filteredResults.Where(s =>
searchCriteria.Tags.Any(tag => s.Tags.Contains(tag, StringComparer.OrdinalIgnoreCase)));
}

// Return filtered snippet results
return JsonSerializer.Serialize(filteredResults.ToList());
}
```

### Supported Property Types

The following property types are supported in `McpToolProperty`:

- `StringPropertyType` - `"string"` for text data
- `ArrayPropertyType` - `"array"` for arrays and lists
- `ObjectPropertyType` - `"object"` for complex objects
- `NumberPropertyType` - `"number"` for numeric data
- `BooleanPropertyType` - `"boolean"` for true/false values

These constants are defined in [`ToolsInformation.cs`](./src/ToolsInformation.cs).

## Next Steps

- Add [API Management](https://github.com/Azure-Samples/remote-mcp-apim-functions-python) to your MCP server
Expand Down
10 changes: 9 additions & 1 deletion src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
// input bindings:
builder
.ConfigureMcpTool(GetSnippetToolName)
.WithProperty(SnippetNamePropertyName, PropertyType, SnippetNamePropertyDescription);
.WithProperty(SnippetNamePropertyName, StringPropertyType, SnippetNamePropertyDescription);

// Example of configuring complex types for the order processing tool
builder
.ConfigureMcpTool("process_order")
.WithProperty("order-items", ArrayPropertyType, "List of order items, each containing item ID, quantity, and price")
.WithProperty("customer-name", StringPropertyType, "Name of the customer placing the order")
.WithProperty("is-urgent", BooleanPropertyType, "Whether this order should be processed urgently")
.WithProperty("discount-percent", NumberPropertyType, "Discount percentage to apply (0-100)");

builder.Build().Run();
124 changes: 122 additions & 2 deletions src/SnippetsTool.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Mcp;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using static FunctionsSnippetTool.ToolsInformation;

namespace FunctionsSnippetTool;
Expand All @@ -9,6 +10,21 @@ public class SnippetsTool(ILogger<SnippetsTool> logger)
{
private const string BlobPath = "snippets/{mcptoolargs." + SnippetNamePropertyName + "}.json";

public class SnippetInfo
{
public string Name { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public List<string> Tags { get; set; } = new();
}

public class SnippetSearchCriteria
{
public List<string> Tags { get; set; } = new();
public string NamePattern { get; set; } = string.Empty;
public bool IncludeContent { get; set; } = true;
}

[Function(nameof(GetSnippet))]
public object GetSnippet(
[McpToolTrigger(GetSnippetToolName, GetSnippetToolDescription)]
Expand All @@ -24,12 +40,116 @@ public object GetSnippet(
public string SaveSnippet(
[McpToolTrigger(SaveSnippetToolName, SaveSnippetToolDescription)]
ToolInvocationContext context,
[McpToolProperty(SnippetNamePropertyName, PropertyType, SnippetNamePropertyDescription)]
[McpToolProperty(SnippetNamePropertyName, StringPropertyType, SnippetNamePropertyDescription)]
string name,
[McpToolProperty(SnippetPropertyName, PropertyType, SnippetPropertyDescription)]
[McpToolProperty(SnippetPropertyName, StringPropertyType, SnippetPropertyDescription)]
string snippet
)
{
return snippet;
}

[Function(nameof(BulkSaveSnippets))]
public string BulkSaveSnippets(
[McpToolTrigger("bulk_save_snippets", "Save multiple code snippets at once")]
ToolInvocationContext context,
[McpToolProperty("snippets", ArrayPropertyType, "Array of snippet objects containing name, content, description, and tags")]
List<SnippetInfo> snippets,
[McpToolProperty("overwrite-existing", BooleanPropertyType, "Whether to overwrite existing snippets with same names")]
bool overwriteExisting = false
)
{
logger.LogInformation("Bulk saving {Count} snippets", snippets.Count);

var results = new List<object>();
foreach (var snippet in snippets)
{
try
{
// In a real implementation, you'd save to blob storage
// For demo purposes, we'll just return success status
results.Add(new
{
Name = snippet.Name,
Status = "Success",
Message = $"Snippet '{snippet.Name}' saved successfully"
});

logger.LogInformation("Saved snippet: {Name}", snippet.Name);
}
catch (Exception ex)
{
results.Add(new
{
Name = snippet.Name,
Status = "Error",
Message = ex.Message
});
logger.LogError(ex, "Failed to save snippet: {Name}", snippet.Name);
}
}

var summary = new
{
TotalProcessed = snippets.Count,
SuccessCount = results.Count(r => ((dynamic)r).Status == "Success"),
Results = results
};

return JsonSerializer.Serialize(summary, new JsonSerializerOptions { WriteIndented = true });
}

[Function(nameof(SearchSnippets))]
public string SearchSnippets(
[McpToolTrigger("search_snippets", "Search for snippets using various criteria")]
ToolInvocationContext context,
[McpToolProperty("search-criteria", ObjectPropertyType, "Search criteria object with tags, name pattern, and content inclusion options")]
SnippetSearchCriteria searchCriteria
)
{
logger.LogInformation("Searching snippets with criteria: tags={Tags}, pattern={Pattern}",
string.Join(",", searchCriteria.Tags), searchCriteria.NamePattern);

// In a real implementation, you'd query blob storage
// For demo purposes, return mock search results
var mockResults = new List<SnippetInfo>
{
new() { Name = "hello-world", Content = "console.log('Hello World');", Description = "Basic hello world", Tags = ["javascript", "basic"] },
new() { Name = "api-request", Content = "fetch('/api/data')", Description = "API request example", Tags = ["javascript", "api"] },
new() { Name = "linq-query", Content = "items.Where(x => x.IsActive)", Description = "LINQ filtering", Tags = ["csharp", "linq"] }
};

// Apply search filters
var filteredResults = mockResults.AsEnumerable();

if (searchCriteria.Tags.Any())
{
filteredResults = filteredResults.Where(s =>
searchCriteria.Tags.Any(tag => s.Tags.Contains(tag, StringComparer.OrdinalIgnoreCase)));
}

if (!string.IsNullOrEmpty(searchCriteria.NamePattern))
{
filteredResults = filteredResults.Where(s =>
s.Name.Contains(searchCriteria.NamePattern, StringComparison.OrdinalIgnoreCase));
}

var results = filteredResults.Select(s => new
{
s.Name,
s.Description,
s.Tags,
Content = searchCriteria.IncludeContent ? s.Content : null
}).ToList();

var searchResults = new
{
SearchCriteria = searchCriteria,
ResultCount = results.Count,
Results = results
};

logger.LogInformation("Search completed. Found {Count} matching snippets", results.Count);
return JsonSerializer.Serialize(searchResults, new JsonSerializerOptions { WriteIndented = true });
}
}
8 changes: 8 additions & 0 deletions src/ToolsInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ internal sealed class ToolsInformation
public const string SnippetNamePropertyDescription = "The name of the snippet.";
public const string SnippetPropertyDescription = "The code snippet.";
public const string PropertyType = "string";

// Property types for MCP tools
public const string StringPropertyType = "string";
public const string ArrayPropertyType = "array";
public const string ObjectPropertyType = "object";
public const string NumberPropertyType = "number";
public const string BooleanPropertyType = "boolean";

public const string HelloToolName = "hello";
public const string HelloToolDescription =
"Simple hello world MCP Tool that responses with a hello message.";
Expand Down