跑通MCP反射通信机制流程

This commit is contained in:
PeterZhong 2025-05-09 00:01:44 +08:00
parent d655680fde
commit c05f19b313
15 changed files with 476 additions and 24 deletions

View File

@ -98,9 +98,11 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="client\" /> <PackageReference Include="log4net" Version="3.1.0-preview.3" />
<Folder Include="resource\" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-preview.3.25171.5" />
<Folder Include="server\" /> <PackageReference Include="ModelContextProtocol" Version="0.1.0-preview.12" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Text.Json" Version="10.0.0-preview.3.25171.5" />
</ItemGroup> </ItemGroup>
<Import Project="C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\Esri.ProApp.SDK.Desktop.targets" Condition="Exists('C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\Esri.ProApp.SDK.Desktop.targets') AND !Exists('Esri.ArcGISPro.Extensions.targets')" /> <Import Project="C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\Esri.ProApp.SDK.Desktop.targets" Condition="Exists('C:\Users\PeterZhong\AppData\Local\Programs\ArcGIS\Pro\bin\Esri.ProApp.SDK.Desktop.targets') AND !Exists('Esri.ArcGISPro.Extensions.targets')" />
</Project> </Project>

View File

@ -18,6 +18,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using log4net.Config;
namespace LinkToolAddin namespace LinkToolAddin
{ {
@ -42,6 +43,7 @@ namespace LinkToolAddin
} }
#endregion Overrides #endregion Overrides
} }
} }

17
client/CallArcGISPro.cs Normal file
View File

@ -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<string,string> parameters)
{
// Call the ArcGIS Pro method and get the result
var result = server.CallArcGISPro.CallArcGISProTool(parameters["toolName"], JsonConvert.DeserializeObject<List<string>>(parameters["toolParams"]));
// Serialize the result back to a JSON string
return JsonConvert.SerializeObject(result);
}
}

29
host/CallMcp.cs Normal file
View File

@ -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<JsonRpcEntity>(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;
}
}
}

235
resource/DocDb.cs Normal file
View File

@ -0,0 +1,235 @@
// <auto-generated />
//
// 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<Node> 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<Uri> 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<Welcome>(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<DateOnly>
{
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<TimeOnly>
{
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<DateTimeOffset>
{
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

9
resource/GpToolDb.cs Normal file
View File

@ -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; }
}

8
resource/PromptDb.cs Normal file
View File

@ -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; }
}

23
server/CallArcGISPro.cs Normal file
View File

@ -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<string> 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;
}
}

25
server/JsonRpcEntity.cs Normal file
View File

@ -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<string,string> Params { get; set; }
[JsonProperty("id")]
public long Id { get; set; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -22,13 +22,7 @@
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<DockPanel Grid.Row="0" LastChildFill="true" KeyboardNavigation.TabNavigation="Local" Height="30"> <DockPanel Grid.Row="0" LastChildFill="true" KeyboardNavigation.TabNavigation="Local" Height="30">
<TextBlock Text="{Binding Heading}" Style="{DynamicResource Esri_TextBlockDockPaneHeader}"> <Button Content="Test Server" Name="TestServer" Click="TestServer_OnClick"></Button>
<TextBlock.ToolTip>
<WrapPanel Orientation="Vertical" MaxWidth="300">
<TextBlock Text="{Binding Heading}" TextWrapping="Wrap"/>
</WrapPanel>
</TextBlock.ToolTip>
</TextBlock>
</DockPanel> </DockPanel>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -1,18 +1,20 @@
using System; using System;
using System.Collections.Generic; using System.ComponentModel;
using System.Linq; using System.IO;
using System.Text; using System.Net.Http;
using System.Threading.Tasks; using System.Net.Http.Headers;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data; using Microsoft.Extensions.DependencyInjection;
using System.Windows.Documents; using Microsoft.Extensions.Hosting;
using System.Windows.Input; using Microsoft.Extensions.Logging;
using System.Windows.Media; using System.Text.Json;
using System.Windows.Media.Imaging; using System.Threading;
using System.Windows.Navigation; using log4net;
using System.Windows.Shapes; using log4net.Appender;
using log4net.Config;
using log4net.Layout;
using ModelContextProtocol.Server;
namespace LinkToolAddin.ui.dockpane namespace LinkToolAddin.ui.dockpane
{ {
@ -21,9 +23,54 @@ namespace LinkToolAddin.ui.dockpane
/// </summary> /// </summary>
public partial class DialogDockpaneView : UserControl public partial class DialogDockpaneView : UserControl
{ {
private static ILog log = LogManager.GetLogger(typeof(DialogDockpaneView));
public DialogDockpaneView() public DialogDockpaneView()
{ {
InitLogger();
InitializeComponent(); 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 日志(严重问题)");
}
} }
} }

View File

@ -17,6 +17,10 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; 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 namespace LinkToolAddin.ui.dockpane
{ {
@ -56,6 +60,7 @@ namespace LinkToolAddin.ui.dockpane
{ {
protected override void OnClick() protected override void OnClick()
{ {
DialogDockpaneViewModel.Show(); DialogDockpaneViewModel.Show();
} }
} }