-
Notifications
You must be signed in to change notification settings - Fork 163
Add Version Check script to canvas configure skill #331
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
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,164 @@ | ||
| using System.Text.Json; | ||
|
|
||
| internal static class Program | ||
| { | ||
| private const string MarketplaceName = "power-platform-skills"; | ||
| private const string DefaultManifestUrl = | ||
| "https://raw.githubusercontent.com/microsoft/power-platform-skills/main/plugins/canvas-apps/.plugin/plugin.json"; | ||
|
|
||
| private static async Task<int> Main(string[] args) | ||
| { | ||
| try | ||
| { | ||
| return await CheckVersion(args); | ||
| } | ||
| catch (Exception) | ||
| { | ||
| // Version discovery is advisory and must never block MCP configuration. | ||
| return 0; | ||
| } | ||
| } | ||
|
|
||
| private static async Task<int> CheckVersion(string[] args) | ||
| { | ||
| string pluginRoot = GetRequiredOption(args, "--plugin-root"); | ||
| string manifestUrl = GetOption(args, "--manifest-url") ?? DefaultManifestUrl; | ||
| PluginManifest local = ReadManifest( | ||
| Path.Combine(pluginRoot, ".plugin", "plugin.json") | ||
| ); | ||
|
lesaltzm marked this conversation as resolved.
|
||
|
|
||
| using var httpClient = new HttpClient | ||
| { | ||
| Timeout = TimeSpan.FromSeconds(5), | ||
| }; | ||
| string remoteJson = await httpClient.GetStringAsync(manifestUrl); | ||
| PluginManifest remote = ParseManifest(remoteJson); | ||
|
|
||
| if (CompareVersions(local.Version, remote.Version) >= 0) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| Console.WriteLine( | ||
| $"Plugin update available: {local.Name} {local.Version} -> {remote.Version}." | ||
| ); | ||
| WriteUpdateCommands(local.Name); | ||
|
|
||
|
lesaltzm marked this conversation as resolved.
|
||
| return 0; | ||
| } | ||
|
|
||
| private static void WriteUpdateCommands(string pluginName) | ||
| { | ||
| string? cli = DetectPluginCli(); | ||
| if (cli is not null) | ||
| { | ||
| Console.WriteLine("Run:"); | ||
| WriteUpdateCommands(cli, pluginName, " "); | ||
| return; | ||
| } | ||
|
|
||
| Console.WriteLine("Run the commands for your CLI:"); | ||
| Console.WriteLine(" Claude Code:"); | ||
| WriteUpdateCommands("claude", pluginName, " "); | ||
| Console.WriteLine(" GitHub Copilot CLI:"); | ||
| WriteUpdateCommands("copilot", pluginName, " "); | ||
| } | ||
|
|
||
| private static void WriteUpdateCommands(string cli, string pluginName, string indent) | ||
| { | ||
| Console.WriteLine($"{indent}{cli} plugin marketplace update {MarketplaceName}"); | ||
| Console.WriteLine( | ||
| $"{indent}{cli} plugin update {pluginName}@{MarketplaceName}" | ||
| ); | ||
| } | ||
|
lesaltzm marked this conversation as resolved.
|
||
|
|
||
| private static string? DetectPluginCli() | ||
| { | ||
| // Match the host detection contract used by shared telemetry. Claude | ||
| // wins if both markers are present, which avoids ambiguous instructions. | ||
| if (IsTruthyEnvironmentVariable("CLAUDECODE")) | ||
| { | ||
| return "claude"; | ||
| } | ||
|
|
||
| return IsTruthyEnvironmentVariable("COPILOT_CLI") ? "copilot" : null; | ||
| } | ||
|
|
||
| private static bool IsTruthyEnvironmentVariable(string name) | ||
| { | ||
| string? value = Environment.GetEnvironmentVariable(name); | ||
| if (value is null) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| string normalized = value.Trim().ToLowerInvariant(); | ||
| return normalized is not ("" or "0" or "false"); | ||
| } | ||
|
|
||
| private static PluginManifest ReadManifest(string path) => | ||
| ParseManifest(File.ReadAllText(path)); | ||
|
|
||
| private static PluginManifest ParseManifest(string json) | ||
| { | ||
| using JsonDocument document = JsonDocument.Parse(json); | ||
|
Contributor
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. why are we not using |
||
| JsonElement root = document.RootElement; | ||
| return new PluginManifest( | ||
| root.GetProperty("name").GetString() | ||
| ?? throw new InvalidDataException("Plugin name is missing."), | ||
| root.GetProperty("version").GetString() | ||
| ?? throw new InvalidDataException("Plugin version is missing.") | ||
| ); | ||
| } | ||
|
|
||
| private static string GetRequiredOption(string[] args, string option) => | ||
| GetOption(args, option) | ||
| ?? throw new ArgumentException($"Missing required option: {option}"); | ||
|
|
||
| private static string? GetOption(string[] args, string option) | ||
| { | ||
| for (int index = 0; index < args.Length - 1; index++) | ||
| { | ||
| if (args[index] == option) | ||
| { | ||
| return args[index + 1]; | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| private static int CompareVersions(string left, string right) | ||
| { | ||
| string[] leftSegments = left.Split('.'); | ||
|
Contributor
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. use System.Version instead? let's not rewrite the parsing logic. and we can use Version.TryParse too |
||
| string[] rightSegments = right.Split('.'); | ||
| int segmentCount = Math.Max(leftSegments.Length, rightSegments.Length); | ||
|
|
||
| for (int index = 0; index < segmentCount; index++) | ||
| { | ||
| int leftSegment = ParseSegment(leftSegments, index, left); | ||
| int rightSegment = ParseSegment(rightSegments, index, right); | ||
| int comparison = leftSegment.CompareTo(rightSegment); | ||
| if (comparison != 0) | ||
| { | ||
| return comparison; | ||
| } | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| private static int ParseSegment(string[] segments, int index, string version) | ||
| { | ||
| if (index >= segments.Length) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| return int.TryParse(segments[index], out int value) | ||
| ? value | ||
| : throw new FormatException($"Invalid plugin version: {version}"); | ||
| } | ||
|
|
||
| private sealed record PluginManifest(string Name, string Version); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,14 @@ | ||
| --- | ||
| name: configure-canvas-mcp | ||
| version: 2.1.0 | ||
| version: 2.1.1 | ||
| description: Configure the Canvas Authoring MCP server for the current coauthoring session. USE WHEN "configure MCP", "set up MCP server", "MCP not working", "connect Canvas Apps MCP", "canvas-authoring not available", "MCP not configured", "set up canvas apps". | ||
| author: Microsoft Corporation | ||
| user-invocable: true | ||
| allowed-tools: Bash, AskUserQuestion, mcp__canvas-authoring__connect | ||
| --- | ||
|
|
||
| > **Plugin check**: Run `dotnet run --file "${PLUGIN_ROOT}/scripts/check-version.cs" --verbosity quiet -- --plugin-root "${PLUGIN_ROOT}"` — if it outputs a message, show it to the user before proceeding. | ||
|
lesaltzm marked this conversation as resolved.
Contributor
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. should we add a TargetFramework?
Contributor
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. oh, this can be set int he cs file. See: https://learn.microsoft.com/en-us/dotnet/core/sdk/file-based-apps#property |
||
|
|
||
| # Configure the Canvas Authoring MCP Server | ||
|
|
||
| This skill configures the Canvas Authoring MCP server for the user's current Power Apps coauthoring session. The MCP server is auto-registered by the plugin — this skill connects it to a specific app session. | ||
|
|
||
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.
consider not returning
intsince it doesn't look like we returning anything other than success.Taskshould be fine I believe.