完整接入MCP工具调用流程

This commit is contained in:
PeterZhong 2025-05-19 21:32:20 +08:00
parent b3e2664acd
commit d9b98df57f
7 changed files with 72 additions and 25 deletions

View File

@ -98,6 +98,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="doc\" />
<Folder Include="resource\" />
</ItemGroup>
<ItemGroup>

View File

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

View File

@ -8,7 +8,9 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using ArcGIS.Desktop.Framework.Dialogs;
using LinkToolAddin.client;
using LinkToolAddin.client.prompt;
using LinkToolAddin.host.llm;
@ -23,6 +25,7 @@ using Microsoft.Extensions.AI;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Types;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using Newtonsoft.Json.Schema.Generation;
using Tool = LinkToolAddin.host.mcp.Tool;
@ -52,8 +55,8 @@ public class Gateway
Content = message
});
bool goOn = true;
string pattern = "<tool_use>[\\s\\S]*?<\\/tool_use>";
string promptPattern = "<prompt>[\\s\\S]*?<\\/prompt>";
string pattern = "^<tool_use>[\\s\\S]*?<\\/tool_use>$";
string promptPattern = "^<prompt>[\\s\\S]*?<\\/prompt>$";
McpServerList mcpServerList = new McpServerList();
while (goOn)
{
@ -65,6 +68,7 @@ public class Gateway
TopP = 1,
MaxTokens = 1000,
});
log.Info(reponse);
messages.Add(new Message
{
Role = "assistant",
@ -213,18 +217,20 @@ public class Gateway
private static async Task<string> GetToolInfos(McpServerList mcpServerList)
{
int loop = 0;
StringBuilder toolInfos = new StringBuilder();
foreach (McpServer mcpServer in mcpServerList.GetAllServers())
{
loop++;
if (loop > 3)
{
MessageBox.Show("达到最大循环次数", "退出循环");
break;
}
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");
InnerMcpServer innerMcpServer = (InnerMcpServer)mcpServer;
Type type = Type.GetType("LinkToolAddin.client.tool." + innerMcpServer.Name);
MethodInfo[] methods = type.GetMethods();
foreach (MethodInfo method in methods)
{
@ -237,12 +243,14 @@ public class Gateway
{
Tool = new Tool
{
Name = methodName,
Name = innerMcpServer.Name + ":" + methodName,
Description = methodDescription,
Arguments = methodParamSchema
}
};
toolInfos.AppendLine(JsonConvert.DeserializeXmlNode(JsonConvert.SerializeObject(toolDefinition)).ToString());
XNode node = JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition));
toolInfos.AppendLine(node.ToString());
toolInfos.AppendLine();
}
}
}
@ -252,7 +260,7 @@ public class Gateway
IList<McpClientTool> tools = await client.GetToolListAsync();
foreach (McpClientTool tool in tools)
{
string toolName = tool.Name;
string toolName = (mcpServer as SseMcpServer).Name + ":" + tool.Name;
string toolDescription = tool.Description;
string toolParamSchema = tool.JsonSchema.ToString();
McpToolDefinition toolDefinition = new McpToolDefinition
@ -265,6 +273,7 @@ public class Gateway
}
};
toolInfos.AppendLine(JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition)).ToString());
toolInfos.AppendLine();
}
}else if (mcpServer is StdioMcpServer)
{
@ -272,7 +281,7 @@ public class Gateway
IList<McpClientTool> tools = await client.GetToolListAsync();
foreach (McpClientTool tool in tools)
{
string toolName = tool.Name;
string toolName = (mcpServer as StdioMcpServer).Name + ":" + tool.Name;;
string toolDescription = tool.Description;
string toolParamSchema = tool.JsonSchema.ToString();
McpToolDefinition toolDefinition = new McpToolDefinition
@ -281,16 +290,25 @@ public class Gateway
{
Name = toolName,
Description = toolDescription,
Arguments = toolParamSchema
Arguments = CompressJson(toolParamSchema)
}
};
toolInfos.AppendLine(JsonConvert.DeserializeXNode(JsonConvert.SerializeObject(toolDefinition)).ToString());
toolInfos.AppendLine();
}
}
}
return toolInfos.ToString();
}
public static string CompressJson(string json)
{
// 解析JSON并自动去除无关空白
var token = JToken.Parse(json);
// 序列化为无格式紧凑字符串
return token.ToString(Newtonsoft.Json.Formatting.None);
}
private static string GenerateMethodParamSchema(MethodInfo method)
{
var generator = new JSchemaGenerator
@ -316,8 +334,11 @@ public class Gateway
paramSchema.Properties.Add(param.Name, typeSchema);
}
return paramSchema.ToString();
var settings = new JsonSerializerSettings {
Formatting = Newtonsoft.Json.Formatting.None, // 关键设置:禁用缩进和换行
NullValueHandling = NullValueHandling.Ignore // 可选:忽略空值
};
return JsonConvert.SerializeObject(paramSchema, settings);;
}
public static async void TestChatMessage(string message, string model, string gdbPath,

View File

@ -2,7 +2,7 @@
public class SystemPrompt
{
public static string SysPromptTemplate = "现在你是一个精通ArcGIS Pro的专家请以此身份回答用户的问题。";
public static string SysPromptTemplate = "现在你是一个精通ArcGIS Pro的专家请以此身份回答用户的问题。你有以下工具可以调用{{toolInfos}},用户的数据库路径是{{gdbPath}}。MCP工具调用的格式要求示例<tool_use>\n <name>search</name>\n <arguments>{\\\"query\\\": \\\"上海 人口\\\"}</arguments>\n</tool_use>";
public static string ContinuePromptTemplate = "上一个工具执行的结果如下,请据此继续执行";

View File

@ -18,11 +18,20 @@
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
<RowDefinition Height="240"/>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" LastChildFill="true" KeyboardNavigation.TabNavigation="Local" Height="30">
<Button Content="Test Workflow" Name="TestWorkflow" Click="TestWorkflow_OnClick"></Button>
</DockPanel>
<Button Grid.Row="0" Content="Test Workflow" Name="TestServer" Click="TestWorkflow_OnClick"></Button>
<Grid Grid.Row="1">
<Grid.RowDefinitions><RowDefinition Height="24"/></Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" ToolTip="输入测试提示词" Name="PromptTestTextBox"></TextBox>
<Button Grid.Row="0" Grid.Column="1" Content="测试" Name="PromptTestButton" Click="PromptTestButton_OnClick"></Button>
</Grid>
<TextBox Grid.Row="2" ToolTip="大模型回复" Name="ReplyTextBox" TextWrapping="Wrap"></TextBox>
</Grid>
</UserControl>

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using LinkToolAddin.client;
@ -146,12 +147,27 @@ 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);
Gateway.TestWorkflow("dafdfdgdagdgui","","",AddReply);
}
public void ShowMessage(MessageListItem msg)
{
log.Info(msg.content);
}
private void PromptTestButton_OnClick(object sender, RoutedEventArgs e)
{
string userPrompt = PromptTestTextBox.Text;
Gateway.SendMessage(userPrompt,"qwen-max","C:/Project/test.gdb",AddReply);
}
public void AddReply(MessageListItem msg)
{
string content = msg.content;
log.Info(content);
string originContent = ReplyTextBox.Text;
ReplyTextBox.Text = originContent + content;
}
}
}

View File

@ -41,7 +41,7 @@ namespace LinkToolAddin.ui.dockpane
/// <summary>
/// Text shown near the top of the DockPane.
/// </summary>
private string _heading = "My DockPane";
private string _heading = "Test Dockpane";
public string Heading
{
get => _heading;