接入MCP ListTool能力补充代码
This commit is contained in:
parent
6be6db2194
commit
b3e2664acd
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
183
host/Gateway.cs
183
host/Gateway.cs
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user