接入MCP ListTool能力补充代码

This commit is contained in:
PeterZhong 2025-05-19 12:14:55 +08:00
parent 6be6db2194
commit b3e2664acd
12 changed files with 327 additions and 26 deletions

View File

@ -106,6 +106,7 @@
<PackageReference Include="ModelContextProtocol" Version="0.1.0-preview.13" />
<PackageReference Include="ModelContextProtocol.AspNetCore" Version="0.1.0-preview.13" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="4.0.2-beta2" />
</ItemGroup>
<Import Project="C:\Program Files\ArcGIS\Pro\bin\Esri.ProApp.SDK.Desktop.targets" Condition="Exists('C:\Program Files\ArcGIS\Pro\bin\Esri.ProApp.SDK.Desktop.targets') AND !Exists('Esri.ArcGISPro.Extensions.targets')" />
</Project>

View File

@ -2,7 +2,7 @@
"profiles": {
"LinkToolAddin": {
"commandName": "Executable",
"executablePath": "C:\\Program Files\\ArcGIS\\Pro\\bin\\ArcGISPro.exe",
"executablePath": "C:\\Users\\PeterZhong\\AppData\\Local\\Programs\\ArcGIS\\Pro\\bin\\ArcGISPro.exe",
"applicationUrl": "https://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"

View File

@ -1,6 +1,21 @@
namespace LinkToolAddin.client.tool;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using LinkToolAddin.server;
using ModelContextProtocol.Server;
using Newtonsoft.Json;
namespace LinkToolAddin.client.tool;
public class ArcGisPro
{
[McpServerTool, Description("ArcGIS Pro Tool")]
public static async Task<JsonRpcResultEntity> ArcGisProTool(string toolName, List<string> toolParams)
{
// Call the ArcGIS Pro method and get the result
var result = await server.CallArcGISPro.CallArcGISProTool(toolName, toolParams);
// Serialize the result back to a JSON string
return result;
}
}

View File

@ -18,8 +18,8 @@ namespace LinkToolAddin.host
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);
Type type = Type.GetType("LinkToolAddin.client.tool"+jsonRpcEntity.Method.Split(':')[0]);
MethodInfo method = type.GetMethod(jsonRpcEntity.Method.Split(':')[1],BindingFlags.Public | BindingFlags.Static);
var task = method.Invoke(null, new object[] { jsonRpcEntity.Params }) as Task<JsonRpcResultEntity>;
JsonRpcResultEntity result = await task;
return JsonConvert.SerializeObject(result);

View File

@ -1,7 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using LinkToolAddin.client;
using LinkToolAddin.client.prompt;
@ -10,13 +16,22 @@ using LinkToolAddin.host.llm.entity;
using LinkToolAddin.host.mcp;
using LinkToolAddin.host.prompt;
using LinkToolAddin.message;
using LinkToolAddin.server;
using LinkToolAddin.ui.dockpane;
using log4net;
using Microsoft.Extensions.AI;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Types;
using Newtonsoft.Json;
using Newtonsoft.Json.Schema;
using Newtonsoft.Json.Schema.Generation;
using Tool = LinkToolAddin.host.mcp.Tool;
namespace LinkToolAddin.host;
public class Gateway
{
private static ILog log = LogManager.GetLogger(typeof(Gateway));
public static async void SendMessage(string message, string model, string gdbPath, Action<MessageListItem> callback)
{
Llm bailian = new Bailian
@ -24,10 +39,12 @@ public class Gateway
api_key = "sk-db177155677e438f832860e7f4da6afc"
};
List<Message> messages = new List<Message>();
string toolInfos = await GetToolInfos(new McpServerList());
log.Info(SystemPrompt.SysPrompt(gdbPath, toolInfos));
messages.Add(new Message
{
Role = "system",
Content = SystemPrompt.SysPromptTemplate
Content = SystemPrompt.SysPrompt(gdbPath, toolInfos)
});
messages.Add(new Message
{
@ -37,7 +54,7 @@ public class Gateway
bool goOn = true;
string pattern = "<tool_use>[\\s\\S]*?<\\/tool_use>";
string promptPattern = "<prompt>[\\s\\S]*?<\\/prompt>";
Dictionary<string,McpServer> servers = new Dictionary<string, McpServer>();
McpServerList mcpServerList = new McpServerList();
while (goOn)
{
string reponse = await bailian.SendChatAsync(new LlmJsonContent()
@ -62,7 +79,7 @@ public class Gateway
Dictionary<string, object> toolParams = JsonConvert.DeserializeObject<Dictionary<string, object>>(toolArgs);
string serverName = fullToolName.Contains(":") ? fullToolName.Split(':')[0] : fullToolName;
string toolName = fullToolName.Contains(":") ? fullToolName.Split(':')[1] : fullToolName;
McpServer mcpServer = servers[serverName];
McpServer mcpServer = mcpServerList.GetServer(serverName);
if (mcpServer is SseMcpServer)
{
SseMcpServer sseMcpServer = mcpServer as SseMcpServer;
@ -111,6 +128,55 @@ public class Gateway
Content = JsonConvert.SerializeObject(toolResponse)
});
callback?.Invoke(toolMessageItem);
}else if (mcpServer is InnerMcpServer)
{
Type type = Type.GetType("LinkToolAddin.client.tool."+serverName);
MethodInfo method = type.GetMethod(toolName,BindingFlags.Public | BindingFlags.Static);
var task = method.Invoke(null, toolParams.Values.ToArray()) as Task<JsonRpcResultEntity>;
JsonRpcResultEntity innerResult = await task;
if (innerResult is JsonRpcErrorEntity)
{
MessageListItem toolMessageItem = new ToolMessageItem
{
toolName = toolName,
toolParams = toolParams,
type = MessageType.TOOL_MESSAGE,
status = "fail",
content = JsonConvert.SerializeObject(innerResult)
};
messages.Add(new Message
{
Role = "user",
Content = SystemPrompt.ErrorPromptTemplate
});
messages.Add(new Message
{
Role = "user",
Content = JsonConvert.SerializeObject(innerResult)
});
callback?.Invoke(toolMessageItem);
}else if (innerResult is JsonRpcSuccessEntity)
{
MessageListItem toolMessageItem = new ToolMessageItem
{
toolName = toolName,
toolParams = toolParams,
type = MessageType.TOOL_MESSAGE,
status = "success",
content = JsonConvert.SerializeObject(innerResult)
};
messages.Add(new Message
{
Role = "user",
Content = SystemPrompt.ContinuePromptTemplate
});
messages.Add(new Message
{
Role = "user",
Content = JsonConvert.SerializeObject(innerResult)
});
callback?.Invoke(toolMessageItem);
}
}
}
else if (Regex.IsMatch(reponse, promptPattern))
@ -145,6 +211,115 @@ public class Gateway
}
}
private static async Task<string> GetToolInfos(McpServerList mcpServerList)
{
StringBuilder toolInfos = new StringBuilder();
foreach (McpServer mcpServer in mcpServerList.GetAllServers())
{
if (mcpServer is InnerMcpServer)
{
string serverName = mcpServer.Name;
if (serverName is null)
{
continue;
}
Type type = Type.GetType("LinkToolAddin.client.tool." + serverName);
Type type2 = Type.GetType("LinkToolAddin.client.tool.ArcGisPro");
MethodInfo[] methods = type.GetMethods();
foreach (MethodInfo method in methods)
{
if (method.IsPublic && method.IsStatic)
{
string methodName = method.Name;
string methodDescription = method.GetCustomAttribute<DescriptionAttribute>()?.Description;
string methodParamSchema = GenerateMethodParamSchema(method);
McpToolDefinition toolDefinition = new McpToolDefinition
{
Tool = new Tool
{
Name = methodName,
Description = methodDescription,
Arguments = methodParamSchema
}
};
toolInfos.AppendLine(JsonConvert.DeserializeXmlNode(JsonConvert.SerializeObject(toolDefinition)).ToString());
}
}
}
else if(mcpServer is SseMcpServer)
{
SseMcpClient client = new SseMcpClient((mcpServer as SseMcpServer).BaseUrl);
IList<McpClientTool> tools = await client.GetToolListAsync();
foreach (McpClientTool tool in tools)
{
string toolName = tool.Name;
string toolDescription = tool.Description;
string toolParamSchema = tool.JsonSchema.ToString();
McpToolDefinition toolDefinition = new McpToolDefinition
{
Tool = new Tool
{
Name = toolName,
Description = toolDescription,
Arguments = toolParamSchema
}
};
toolInfos.AppendLine(JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition)).ToString());
}
}else if (mcpServer is StdioMcpServer)
{
StdioMcpClient client = new StdioMcpClient((mcpServer as StdioMcpServer).Command, (mcpServer as StdioMcpServer).Args);
IList<McpClientTool> tools = await client.GetToolListAsync();
foreach (McpClientTool tool in tools)
{
string toolName = tool.Name;
string toolDescription = tool.Description;
string toolParamSchema = tool.JsonSchema.ToString();
McpToolDefinition toolDefinition = new McpToolDefinition
{
Tool = new Tool
{
Name = toolName,
Description = toolDescription,
Arguments = toolParamSchema
}
};
toolInfos.AppendLine(JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition)).ToString());
}
}
}
return toolInfos.ToString();
}
private static string GenerateMethodParamSchema(MethodInfo method)
{
var generator = new JSchemaGenerator
{
// 启用属性注解处理
DefaultRequired = Required.DisallowNull,
SchemaReferenceHandling = SchemaReferenceHandling.None
};
var paramSchema = new JSchema { Type = JSchemaType.Object };
foreach (ParameterInfo param in method.GetParameters())
{
// 生成参数类型的基础Schema
JSchema typeSchema = generator.Generate(param.ParameterType);
// 添加Description描述
var descriptionAttr = param.GetCustomAttribute<DescriptionAttribute>();
if (descriptionAttr != null)
{
typeSchema.Description = descriptionAttr.Description; // 网页6的Description特性处理
}
paramSchema.Properties.Add(param.Name, typeSchema);
}
return paramSchema.ToString();
}
public static async void TestChatMessage(string message, string model, string gdbPath,
Action<MessageListItem> callback)
{
@ -178,7 +353,7 @@ public class Gateway
callback?.Invoke(toolListItem);
}
public static async void TestWOrkflow(string message, string model, string gdbPath, Action<MessageListItem> callback)
public static async void TestWorkflow(string message, string model, string gdbPath, Action<MessageListItem> callback)
{
Thread.Sleep(2000);
MessageListItem chatListItem = new ChatMessageItem

View File

@ -1,6 +1,54 @@
namespace LinkToolAddin.host;
using System.Collections.Generic;
using LinkToolAddin.host.mcp;
namespace LinkToolAddin.host;
public class McpServerList
{
private Dictionary<string,McpServer> servers = new Dictionary<string, McpServer>();
public McpServerList()
{
servers.Add("gaode",new SseMcpServer
{
Name = "gaode",
Type = "sse",
Description = "高德地图API",
IsActive = true,
BaseUrl = "https://mcp.amap.com/sse?key=ed418512c94ade8f83d42c37b77d2bb2",
Headers = new Dictionary<string, string>()
{
{"Content-Type","application/json"}
}
});
servers.Add("arcgis", new InnerMcpServer
{
Name = "ArcGisPro",
Type = "inner",
Description = "可以调用arcgis的地理处理工具或执行python代码等",
IsActive = true
});
}
public McpServer GetServer(string name)
{
if (servers.ContainsKey(name))
{
return servers[name];
}
else
{
return null;
}
}
public List<McpServer> GetAllServers()
{
List<McpServer> serverList = new List<McpServer>();
foreach (var server in servers)
{
serverList.Add(server.Value);
}
return serverList;
}
}

View File

@ -1,6 +1,24 @@
namespace LinkToolAddin.host.mcp;
public class InnerMcpServer
namespace LinkToolAddin.host.mcp
{
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public partial class InnerMcpServer : McpServer
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("isActive")]
public bool IsActive { get; set; }
}
}

View File

@ -1,6 +1,27 @@
namespace LinkToolAddin.host.mcp;
public class McpDefinition
namespace LinkToolAddin.host.mcp
{
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public partial class McpToolDefinition
{
[JsonProperty("tool")]
public Tool Tool { get; set; }
}
public partial class Tool
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("arguments")]
public string Arguments { get; set; }
}
}

View File

@ -8,4 +8,12 @@ public class SystemPrompt
public static string ErrorPromptTemplate = "执行上一个工具的时候出现以下错误,请根据报错信息重试";
public static string SysPrompt(string gdbPath, string toolInfos)
{
string sysPrompt = SysPromptTemplate;
sysPrompt = sysPrompt.Replace("{{gdbPath}}", gdbPath);
sysPrompt = sysPrompt.Replace("{{toolInfos}}", toolInfos);
return sysPrompt;
}
}

View File

@ -10,14 +10,29 @@ namespace LinkToolAddin.server;
public class CallArcGISPro
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(CallArcGISPro));
public async static Task<JsonRpcResultEntity> CallArcGISProTool(string toolName, List<string> toolParams)
{
var results = await Geoprocessing.ExecuteToolAsync(toolName, toolParams);
log.Info($"CallArcGISProTool: {toolName} | {toolParams}");
return new JsonRpcSuccessEntity()
JsonRpcResultEntity jsonRpcResultEntity;
if (results.ErrorCode == 0)
{
Id = 1,
Result = results.ToString()
};
jsonRpcResultEntity = new JsonRpcErrorEntity()
{
Error = new Error()
{
Code = results.ErrorCode,
Message = results.ErrorMessages.ToString()
}
};
}
else
{
jsonRpcResultEntity = new JsonRpcSuccessEntity
{
Result = results.Messages.ToString()
};
}
return jsonRpcResultEntity;
}
}

View File

@ -2,10 +2,10 @@ namespace LinkToolAddin.server
{
using Newtonsoft.Json;
public partial class JsonRpcErrorEntity
public partial class JsonRpcErrorEntity : JsonRpcResultEntity
{
[JsonProperty("jsonrpc")]
public string Jsonrpc { get; set; }
public string Jsonrpc { get; set; } = "2.0";
[JsonProperty("error")]
public Error Error { get; set; }

View File

@ -146,7 +146,7 @@ namespace LinkToolAddin.ui.dockpane
private void TestWorkflow_OnClick(object sender, RoutedEventArgs e)
{
Gateway.SendMessage("你","qwen-max","test.gdb",ShowMessage);
Gateway.SendMessage("你有什么工具","qwen-max","test.gdb",ShowMessage);
}
public void ShowMessage(MessageListItem msg)