From c05f19b313f5fbf0dc369ea6bc4697117677e79a Mon Sep 17 00:00:00 2001 From: PeterZhong Date: Fri, 9 May 2025 00:01:44 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B7=91=E9=80=9AMCP=E5=8F=8D=E5=B0=84?= =?UTF-8?q?=E9=80=9A=E4=BF=A1=E6=9C=BA=E5=88=B6=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LinkToolAddin.csproj | 8 +- LinkToolModule.cs | 4 +- client/CallArcGISPro.cs | 17 ++ host/CallMcp.cs | 29 +++ resource/DocDb.cs | 235 +++++++++++++++++++++++++ resource/GpToolDb.cs | 9 + resource/PromptDb.cs | 8 + server/CallArcGISPro.cs | 23 +++ server/JsonRpcEntity.cs | 25 +++ server/JsonRpcErrorEntity.cs | 25 +++ server/JsonRpcResultEntity.cs | 15 ++ server/JsonRpcSuccessEntity.cs | 16 ++ ui/dockpane/DialogDockpane.xaml | 8 +- ui/dockpane/DialogDockpane.xaml.cs | 73 ++++++-- ui/dockpane/DialogDockpaneViewModel.cs | 5 + 15 files changed, 476 insertions(+), 24 deletions(-) create mode 100644 client/CallArcGISPro.cs create mode 100644 host/CallMcp.cs create mode 100644 resource/DocDb.cs create mode 100644 resource/GpToolDb.cs create mode 100644 resource/PromptDb.cs create mode 100644 server/CallArcGISPro.cs create mode 100644 server/JsonRpcEntity.cs create mode 100644 server/JsonRpcErrorEntity.cs create mode 100644 server/JsonRpcResultEntity.cs create mode 100644 server/JsonRpcSuccessEntity.cs diff --git a/LinkToolAddin.csproj b/LinkToolAddin.csproj index 818ba95..299d12b 100644 --- a/LinkToolAddin.csproj +++ b/LinkToolAddin.csproj @@ -98,9 +98,11 @@ - - - + + + + + diff --git a/LinkToolModule.cs b/LinkToolModule.cs index 1d4476d..30107d1 100644 --- a/LinkToolModule.cs +++ b/LinkToolModule.cs @@ -18,6 +18,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; +using log4net.Config; namespace LinkToolAddin { @@ -42,6 +43,7 @@ namespace LinkToolAddin } #endregion Overrides - } + + } diff --git a/client/CallArcGISPro.cs b/client/CallArcGISPro.cs new file mode 100644 index 0000000..c04c526 --- /dev/null +++ b/client/CallArcGISPro.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using LinkToolAddin.server; +using Newtonsoft.Json; + +namespace LinkToolAddin.client; + +public class CallArcGISPro +{ + public static string CallArcGISProTool(Dictionary parameters) + { + // Call the ArcGIS Pro method and get the result + var result = server.CallArcGISPro.CallArcGISProTool(parameters["toolName"], JsonConvert.DeserializeObject>(parameters["toolParams"])); + + // Serialize the result back to a JSON string + return JsonConvert.SerializeObject(result); + } +} \ No newline at end of file diff --git a/host/CallMcp.cs b/host/CallMcp.cs new file mode 100644 index 0000000..b27178c --- /dev/null +++ b/host/CallMcp.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using LinkToolAddin.server; +using log4net; +using Newtonsoft.Json; + +namespace LinkToolAddin.host +{ + public class CallMcp + { + private static readonly ILog log = LogManager.GetLogger(typeof(CallMcp)); + public static string CallInnerMcpTool(string jsonRpcString) + { + log.Info("通过反射调用内部MCP工具"); + var jsonRpcEntity = JsonConvert.DeserializeObject(jsonRpcString); + + Type type = Type.GetType("LinkToolAddin.client."+jsonRpcEntity.Method.Split('.')[0]); + MethodInfo method = type.GetMethod(jsonRpcEntity.Method.Split('.')[1],BindingFlags.Public | BindingFlags.Static); + string result = (string)method.Invoke(null, new object[] { jsonRpcEntity.Params }); + + // 将结果序列化为 JSON 字符串 + return result; + } + } +} \ No newline at end of file diff --git a/resource/DocDb.cs b/resource/DocDb.cs new file mode 100644 index 0000000..3164327 --- /dev/null +++ b/resource/DocDb.cs @@ -0,0 +1,235 @@ +// +// +// To parse this JSON data, add NuGet 'System.Text.Json' then do: +// +// using QuickType; +// +// var welcome = Welcome.FromJson(jsonString); +#nullable enable +#pragma warning disable CS8618 +#pragma warning disable CS8601 +#pragma warning disable CS8603 + +namespace QuickType +{ + using System; + using System.Collections.Generic; + + using System.Text.Json; + using System.Text.Json.Serialization; + using System.Globalization; + + public partial class DocDb + { + [JsonPropertyName("Code")] + public string Code { get; set; } + + [JsonPropertyName("Data")] + public Data Data { get; set; } + + [JsonPropertyName("Message")] + public string Message { get; set; } + + [JsonPropertyName("RequestId")] + public string RequestId { get; set; } + + [JsonPropertyName("Status")] + public long Status { get; set; } + + [JsonPropertyName("Success")] + public bool Success { get; set; } + } + + public partial class Data + { + [JsonPropertyName("Nodes")] + public List Nodes { get; set; } + } + + public partial class Node + { + [JsonPropertyName("Metadata")] + public Metadata Metadata { get; set; } + + [JsonPropertyName("Score")] + public double Score { get; set; } + + [JsonPropertyName("Text")] + public string Text { get; set; } + } + + public partial class Metadata + { + [JsonPropertyName("parent")] + public string Parent { get; set; } + + [JsonPropertyName("file_path")] + public Uri FilePath { get; set; } + + [JsonPropertyName("image_url")] + public List ImageUrl { get; set; } + + [JsonPropertyName("nid")] + public string Nid { get; set; } + + [JsonPropertyName("title")] + public string Title { get; set; } + + [JsonPropertyName("doc_id")] + public string DocId { get; set; } + + [JsonPropertyName("content")] + public string Content { get; set; } + + [JsonPropertyName("workspace_id")] + public string WorkspaceId { get; set; } + + [JsonPropertyName("hier_title")] + public string HierTitle { get; set; } + + [JsonPropertyName("doc_name")] + public string DocName { get; set; } + + [JsonPropertyName("pipeline_id")] + public string PipelineId { get; set; } + + [JsonPropertyName("_id")] + public string Id { get; set; } + } + + public partial class Welcome + { + public static Welcome FromJson(string json) => JsonSerializer.Deserialize(json, QuickType.Converter.Settings); + } + + public static class Serialize + { + public static string ToJson(this Welcome self) => JsonSerializer.Serialize(self, QuickType.Converter.Settings); + } + + internal static class Converter + { + public static readonly JsonSerializerOptions Settings = new(JsonSerializerDefaults.General) + { + Converters = + { + new DateOnlyConverter(), + new TimeOnlyConverter(), + IsoDateTimeOffsetConverter.Singleton + }, + }; + } + + public class DateOnlyConverter : JsonConverter + { + private readonly string serializationFormat; + public DateOnlyConverter() : this(null) { } + + public DateOnlyConverter(string? serializationFormat) + { + this.serializationFormat = serializationFormat ?? "yyyy-MM-dd"; + } + + public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return DateOnly.Parse(value!); + } + + public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(serializationFormat)); + } + + public class TimeOnlyConverter : JsonConverter + { + private readonly string serializationFormat; + + public TimeOnlyConverter() : this(null) { } + + public TimeOnlyConverter(string? serializationFormat) + { + this.serializationFormat = serializationFormat ?? "HH:mm:ss.fff"; + } + + public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return TimeOnly.Parse(value!); + } + + public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(serializationFormat)); + } + + internal class IsoDateTimeOffsetConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(DateTimeOffset); + + private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK"; + + private DateTimeStyles _dateTimeStyles = DateTimeStyles.RoundtripKind; + private string? _dateTimeFormat; + private CultureInfo? _culture; + + public DateTimeStyles DateTimeStyles + { + get => _dateTimeStyles; + set => _dateTimeStyles = value; + } + + public string? DateTimeFormat + { + get => _dateTimeFormat ?? string.Empty; + set => _dateTimeFormat = (string.IsNullOrEmpty(value)) ? null : value; + } + + public CultureInfo Culture + { + get => _culture ?? CultureInfo.CurrentCulture; + set => _culture = value; + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + string text; + + + if ((_dateTimeStyles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal + || (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal) + { + value = value.ToUniversalTime(); + } + + text = value.ToString(_dateTimeFormat ?? DefaultDateTimeFormat, Culture); + + writer.WriteStringValue(text); + } + + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? dateText = reader.GetString(); + + if (string.IsNullOrEmpty(dateText) == false) + { + if (!string.IsNullOrEmpty(_dateTimeFormat)) + { + return DateTimeOffset.ParseExact(dateText, _dateTimeFormat, Culture, _dateTimeStyles); + } + else + { + return DateTimeOffset.Parse(dateText, Culture, _dateTimeStyles); + } + } + else + { + return default(DateTimeOffset); + } + } + + + public static readonly IsoDateTimeOffsetConverter Singleton = new IsoDateTimeOffsetConverter(); + } +} +#pragma warning restore CS8618 +#pragma warning restore CS8601 +#pragma warning restore CS8603 diff --git a/resource/GpToolDb.cs b/resource/GpToolDb.cs new file mode 100644 index 0000000..3706c8d --- /dev/null +++ b/resource/GpToolDb.cs @@ -0,0 +1,9 @@ +namespace LinkToolAddin.resource; + +public class GpToolDb +{ + public string toolName { get; set; } + public string toolDescription { get; set; } + public string exeName { get; set; } + public string toolParma { get; set; } +} \ No newline at end of file diff --git a/resource/PromptDb.cs b/resource/PromptDb.cs new file mode 100644 index 0000000..6b6e6d3 --- /dev/null +++ b/resource/PromptDb.cs @@ -0,0 +1,8 @@ +namespace LinkToolAddin.resource; + +public class PromptDb +{ + public string promptName { get; set; } + public string promptDescription { get; set; } + public string promptContent { get; set; } +} \ No newline at end of file diff --git a/server/CallArcGISPro.cs b/server/CallArcGISPro.cs new file mode 100644 index 0000000..a86ba63 --- /dev/null +++ b/server/CallArcGISPro.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using ArcGIS.Desktop.Framework.Dialogs; + +namespace LinkToolAddin.server; + +public class CallArcGISPro +{ + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(CallArcGISPro)); + public static JsonRpcResultEntity CallArcGISProTool(string methodName, List methodParams) + { + // Simulate a call to ArcGIS Pro and return a success response + var successResponse = new JsonRpcSuccessEntity + { + Jsonrpc = "2.0", + Id = 1, + Result = $"ArcGIS Pro {methodName} method called successfully" + }; + log.Info($"ArcGIS Pro {methodName} method called successfully"); + MessageBox.Show($"ArcGIS Pro {methodName} method called successfully", "Test JsonRpc"); + return successResponse; + } +} \ No newline at end of file diff --git a/server/JsonRpcEntity.cs b/server/JsonRpcEntity.cs new file mode 100644 index 0000000..01a91e3 --- /dev/null +++ b/server/JsonRpcEntity.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace LinkToolAddin.server +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + + public partial class JsonRpcEntity + { + [JsonProperty("jsonrpc")] + public string Jsonrpc { get; set; } + + [JsonProperty("method")] + public string Method { get; set; } + + [JsonProperty("params")] + public Dictionary Params { get; set; } + + [JsonProperty("id")] + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/server/JsonRpcErrorEntity.cs b/server/JsonRpcErrorEntity.cs new file mode 100644 index 0000000..46381de --- /dev/null +++ b/server/JsonRpcErrorEntity.cs @@ -0,0 +1,25 @@ +namespace LinkToolAddin.server +{ + using Newtonsoft.Json; + + public partial class JsonRpcErrorEntity + { + [JsonProperty("jsonrpc")] + public string Jsonrpc { get; set; } + + [JsonProperty("error")] + public Error Error { get; set; } + + [JsonProperty("id")] + public long Id { get; set; } + } + + public partial class Error + { + [JsonProperty("code")] + public long Code { get; set; } + + [JsonProperty("message")] + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/server/JsonRpcResultEntity.cs b/server/JsonRpcResultEntity.cs new file mode 100644 index 0000000..5d80803 --- /dev/null +++ b/server/JsonRpcResultEntity.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; + +namespace LinkToolAddin.server +{ + using Newtonsoft.Json; + + public partial class JsonRpcResultEntity + { + [JsonProperty("jsonrpc")] + public string Jsonrpc { get; set; } + + [JsonProperty("id")] + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/server/JsonRpcSuccessEntity.cs b/server/JsonRpcSuccessEntity.cs new file mode 100644 index 0000000..a37985f --- /dev/null +++ b/server/JsonRpcSuccessEntity.cs @@ -0,0 +1,16 @@ +namespace LinkToolAddin.server +{ + using Newtonsoft.Json; + + public partial class JsonRpcSuccessEntity : JsonRpcResultEntity + { + [JsonProperty("jsonrpc")] + public string Jsonrpc { get; set; } + + [JsonProperty("result")] + public string Result { get; set; } + + [JsonProperty("id")] + public long Id { get; set; } + } +} \ No newline at end of file diff --git a/ui/dockpane/DialogDockpane.xaml b/ui/dockpane/DialogDockpane.xaml index 3b569d7..84e7de1 100644 --- a/ui/dockpane/DialogDockpane.xaml +++ b/ui/dockpane/DialogDockpane.xaml @@ -22,13 +22,7 @@ - - - - - - - + \ No newline at end of file diff --git a/ui/dockpane/DialogDockpane.xaml.cs b/ui/dockpane/DialogDockpane.xaml.cs index 4a594fd..0196a6c 100644 --- a/ui/dockpane/DialogDockpane.xaml.cs +++ b/ui/dockpane/DialogDockpane.xaml.cs @@ -1,18 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System; +using System.ComponentModel; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; - +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System.Text.Json; +using System.Threading; +using log4net; +using log4net.Appender; +using log4net.Config; +using log4net.Layout; +using ModelContextProtocol.Server; namespace LinkToolAddin.ui.dockpane { @@ -21,9 +23,54 @@ namespace LinkToolAddin.ui.dockpane /// public partial class DialogDockpaneView : UserControl { + private static ILog log = LogManager.GetLogger(typeof(DialogDockpaneView)); + public DialogDockpaneView() { + InitLogger(); InitializeComponent(); } + + private async void TestServer_OnClick(object sender, RoutedEventArgs e) + { + log.Info("TestServer Clicked"); + string jsonRpcString = @"{""jsonrpc"":""2.0"",""method"":""CallArcGISPro.CallArcGISProTool"",""params"":{""toolName"":""Testbbb"",""toolParams"":""[\""aa\"",\""bbb\""]""},""id"":1}"; + LinkToolAddin.host.CallMcp.CallInnerMcpTool(jsonRpcString); + } + + protected void InitLogger() + { + // 1. 创建控制台输出器(Appender) + var consoleAppender = new ConsoleAppender + { + Layout = new PatternLayout("%date [%thread] %-5level %logger - %message%newline"), + Threshold = log4net.Core.Level.Info // 仅输出 Info 及以上级别 + }; + consoleAppender.ActivateOptions(); // 激活配置 + + // 2. 创建文件滚动输出器(按大小滚动) + var fileAppender = new RollingFileAppender + { + File = Path.Combine("Logs", "app.log"), // 日志文件路径 + AppendToFile = true, // 追加模式 + RollingStyle = RollingFileAppender.RollingMode.Size, // 按文件大小滚动 + MaxSizeRollBackups = 10, // 保留 10 个历史文件 + MaximumFileSize = "1MB", // 单个文件最大 1MB + StaticLogFileName = true, // 固定文件名(否则自动追加序号) + Layout = new PatternLayout("%date [%thread] %-5level %logger - %message%newline"), + Threshold = log4net.Core.Level.Info // 仅输出 Info 及以上级别 + }; + fileAppender.ActivateOptions(); // 激活配置 + + // 3. 直接通过 BasicConfigurator 注册 Appender + BasicConfigurator.Configure(consoleAppender, fileAppender); + + log = LogManager.GetLogger(typeof(DialogDockpaneView)); + + // 测试日志输出 + log.Debug("Debug 日志(控制台可见)"); + log.Info("Info 日志(控制台和文件可见)"); + log.Error("Error 日志(严重问题)"); + } } } diff --git a/ui/dockpane/DialogDockpaneViewModel.cs b/ui/dockpane/DialogDockpaneViewModel.cs index 5871201..1d7a4e5 100644 --- a/ui/dockpane/DialogDockpaneViewModel.cs +++ b/ui/dockpane/DialogDockpaneViewModel.cs @@ -17,6 +17,10 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; namespace LinkToolAddin.ui.dockpane { @@ -56,6 +60,7 @@ namespace LinkToolAddin.ui.dockpane { protected override void OnClick() { + DialogDockpaneViewModel.Show(); } }