在 Azure 上增强 AI 工作流:远程 MCP 工具触发器 + 你的第一个 TypeScript MCP 服务器
本文介绍了如何在Azure上使用远程MCP工具触发器构建AI工作流。通过模型上下文协议(MCP)和Azure Functions的结合,开发者可以创建灵活可扩展的AI工具,实现云服务与AI助手的深度集成。文章以智能代码片段管理系统为例,展示了如何利用TypeScript开发MCP服务器,实现代码片段的存储、检索和管理功能。这种方案不仅提高了AI工作流的灵活性,还能充分利用Azure无服务器计算的优
简简单单 Online zuozuo :本心、输入输出、结果
文章目录
在 Azure 上增强 AI 工作流:远程 MCP 工具触发器 + 你的第一个 TypeScript MCP 服务器
编辑 | 简简单单 Online zuozuo
地址 | https://blog.csdn.net/qq_15071263
如果觉得本文对你有帮助,欢迎关注、点赞、收藏、评论,谢谢
前言
在当今的 AI 应用开发中,智能代理(Agent)应用的工作流程通常从用户通过聊天界面或表单提交提示开始。代理接收提示后,会分析用户意图和需求,使用大语言模型(LLM)来获取任务、澄清细节,并将整体任务分解为子任务。
一旦代理清楚理解了目标,它就会选择最合适的专业工具或服务来实现目标。这些工具可能包括 API、数据库、生成式 AI(用于写作、图像生成等)或其他合作伙伴系统。根据任务的复杂程度,代理可能会安排或组合多个工具操作。
代理会持续评估和调整,重复调用工具,如果输出不足或不清楚,还会请求进一步澄清。在获得有形的或高质量的结果后,代理会合并输出,格式化响应,并将其返回给用户进行审查。这种自适应过程通过充分利用集成的工具和智能规划,为用户提供相关且有时甚至更优的答案。
然而,当前的架构缺乏灵活性,无法实现所有工具,这意味着工具只是特定代理应用程序的一部分。没有通用的方法来发布此工具供其他系统或应用程序使用。这就是模型上下文协议(Model Context Protocol,MCP)发挥作用的地方。
#Azure Functions #MCP #AI工作流 #TypeScript #无服务器计算 #模型上下文协议 #AI助手 #云原生

1、为什么需要标准协议?

当前的架构缺乏灵活性,无法实现所有工具,这意味着工具只是特定代理应用程序的一部分。没有通用的方法来发布此工具供其他系统或应用程序使用。
模型上下文协议(MCP) 正是解决这个问题的方案。MCP 是由 Anthropic 创建的系统,它不仅使工具易于插入单个应用程序,还允许许多不同的应用程序通过共享的、标准的方式来描述和公开功能,从而连接到这些工具。MCP 采用客户端-服务器架构建模。
其结构如下:服务器是通过标准 MCP 接口传达其特定功能的应用程序。客户端是使用这些功能来创建智能、深度集成体验的其他应用程序。下面给出了经典 MCP 架构的概述。
2、Microsoft Azure Functions 的帮助

代理缺乏灵活性的问题由 Microsoft 的解决方案解决,该解决方案包括最近在 Azure Functions 中发布的远程 MCP 功能。这是在开发智能 AI 工作流和工具方面向前迈出的一步,可以快速准确地连接云服务并与之协作。
通过直接在 Azure Functions 中使用 MCP,开发人员可以首次创建强大的、可扩展的 AI 工具,这些工具可以利用备受推崇的 Azure 平台的完整功能,同时由于无服务器计算,运行起来既轻松又经济。
3、什么是远程 MCP?

远程 MCP 是一项功能,允许将 Azure Functions 配置为 MCP 服务器,这些服务器公开自定义工具,GitHub Copilot、Claude 或其他 MCP 兼容客户端等 AI 助手可以发现和使用这些工具。
MCP 允许将 AI 逻辑连接到云原生操作,从而使 AI 代理能够:
- 从 Azure 存储读取和写入数据
- 查询数据库
- 处理文件和文档
- 与第三方 API 集成
- 在云中执行复杂的业务逻辑
4、实际用例:智能代码片段管理

让我们探索一个实际场景,开发人员需要一个 AI 驱动的代码片段管理系统,该系统可以:
- 保存和检索可重用的代码片段
- 按名称和内容组织代码片段
- 分析和总结多个代码片段
该系统演示了使用 MCP 工具触发器和 Azure Functions 输入和输出绑定的远程 MCP,这是它们最简单的形式。
实现
1. 代码片段存储和检索
import { app, InvocationContext, input, output, arg } from "@azure/functions";
// 与 C# ToolsInformation 类匹配的常量
const GET_SNIPPET_TOOL_NAME = "getsnippets";
const GET_SNIPPET_TOOL_DESCRIPTION = "Gets code snippets from your snippet collection.";
const SAVE_SNIPPET_TOOL_NAME = "savesnippet";
const SAVE_SNIPPET_TOOL_DESCRIPTION = "Saves a code snippet into your snippet collection.";
const SNIPPET_NAME_PROPERTY_NAME = "snippetname";
const SNIPPET_PROPERTY_NAME = "snippet";
const SNIPPET_NAME_PROPERTY_DESCRIPTION = "The name of the snippet.";
const SNIPPET_PROPERTY_DESCRIPTION = "The code snippet.";
// 定义 blob 输入和输出绑定
const blobInputBinding = input.storageBlob({
connection: "AzureWebJobsStorage",
path: `snippets/{mcptoolargs.${SNIPPET_NAME_PROPERTY_NAME}}.json`,
});
const blobOutputBinding = output.storageBlob({
connection: "AzureWebJobsStorage",
path: `snippets/{mcptoolargs.${SNIPPET_NAME_PROPERTY_NAME}}.json`,
});
// GetSnippet 函数 - 按名称检索代码片段
export async function getSnippet(
_toolArguments: unknown,
context: InvocationContext
): Promise<string> {
console.info("Getting snippet");
// 从工具参数中获取代码片段名称
const mcptoolargs = context.triggerMetadata.mcptoolargs as { snippetname?: string; };
const snippetName = mcptoolargs?.snippetname;
console.info(`Snippet name: ${snippetName}`);
if (!snippetName) {
return "No snippet name provided";
}
// 从 blob 绑定获取内容 - 正确地从 extraInputs 检索
const snippetContent = context.extraInputs.get(blobInputBinding);
if (!snippetContent) {
return `Snippet '${snippetName}' not found`;
}
console.info(`Retrieved snippet: ${snippetName}`);
return snippetContent as string;
}
// SaveSnippet 函数 - 使用名称保存代码片段
export async function saveSnippet(
_toolArguments: unknown,
context: InvocationContext
): Promise<string> {
console.info("Saving snippet");
// 从工具参数中获取代码片段名称和内容
const mcptoolargs = context.triggerMetadata.mcptoolargs as { snippetname?: string; snippet?: string; };
const snippetName = mcptoolargs?.snippetname;
const snippet = mcptoolargs?.snippet;
if (!snippetName) {
return "No snippet name provided";
}
if (!snippet) {
return "No snippet content provided";
}
// 使用输出绑定将代码片段保存到 blob 存储
context.extraOutputs.set(blobOutputBinding, snippet);
console.info(`Saved snippet: ${snippetName}`);
return snippet;
}
// 注册 GetSnippet 工具
app.mcpTool("getSnippet", {
toolName: GET_SNIPPET_TOOL_NAME,
description: GET_SNIPPET_TOOL_DESCRIPTION,
toolProperties: {
[SNIPPET_NAME_PROPERTY_NAME]: arg.string().describe(SNIPPET_NAME_PROPERTY_DESCRIPTION)
},
extraInputs: [blobInputBinding],
handler: getSnippet,
});
// 注册 SaveSnippet 工具
app.mcpTool("saveSnippet", {
toolName: SAVE_SNIPPET_TOOL_NAME,
description: SAVE_SNIPPET_TOOL_DESCRIPTION,
toolProperties: {
[SNIPPET_NAME_PROPERTY_NAME]: arg.string().describe(SNIPPET_NAME_PROPERTY_DESCRIPTION),
[SNIPPET_PROPERTY_NAME]: arg.string().describe(SNIPPET_PROPERTY_DESCRIPTION)
},
extraOutputs: [blobOutputBinding],
handler: saveSnippet,
});
2. 智能代码片段分析
import { BlobServiceClient } from "@azure/storage-blob";
// 一次总结多个代码片段
export async function summarizeSnippets(
_toolArguments: unknown,
context: InvocationContext
): Promise<string> {
console.info("Summarizing snippets");
try {
// 获取存储连接字符串
const connectionString = process.env.AzureWebJobsStorage;
if (!connectionString) {
return "Azure Storage connection string not configured";
}
// 创建 blob 服务客户端
const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
const containerClient = blobServiceClient.getContainerClient("snippets");
if (!(await containerClient.exists())) {
return "No snippets container found";
}
let summary = `**Snippets Summary**\n\n`;
let totalWords = 0;
let totalChars = 0;
// 读取并显示所有代码片段
for await (const blob of containerClient.listBlobsFlat()) {
const blobClient = containerClient.getBlobClient(blob.name);
const response = await blobClient.download();
if (response.readableStreamBody) {
const chunks = [];
for await (const chunk of response.readableStreamBody) {
chunks.push(chunk);
}
const content = Buffer.concat(chunks).toString();
const words = content.trim().split(/\s+/).length;
const chars = content.length;
totalWords += words;
totalChars += chars;
const fileName = blob.name.replace('.json', '');
summary += `**${fileName}** (${words} words, ${chars} chars)\n`;
summary += `\`\`\`\n${content}\n\`\`\`\n\n`;
}
}
summary = `**Snippets Summary** (${totalWords} total words, ${totalChars} total chars)\n\n` + summary;
return summary;
} catch (error) {
console.error("Error in summarizeSnippets:", error);
return `Error generating summary: ${error instanceof Error ? error.message : 'Unknown error'}`;
}
}
// 注册 SummarizeSnippets 工具
app.mcpTool("summarizeSnippets", {
toolName: "summarize_snippets",
description: "Analyze and summarize all code snippets in storage with statistics and insights",
toolProperties: {
// 不需要参数 - 分析存储中的所有代码片段
},
handler: summarizeSnippets,
});
3. 与 AI 助手的集成
部署后,AI 助手可以发现并使用这些工具:
GitHub Copilot Chat 示例:
用户:“存储一个关于 TypeScript 最佳实践的新文档”
AI:我会帮你存储该文档。让我使用文档管理系统。
用户:“查找所有与 Azure 相关的文档”
AI:我会搜索包含 Azure 相关内容的文档。
5、为什么使用 Azure 无服务器平台?

无服务器可扩展性: Azure Functions 根据需求自动扩展,可以处理从单个文档请求到数千个并发操作的所有内容。
丰富的集成生态系统: 通过内置绑定,你可以轻松集成:
- Azure 存储(Blobs、Tables、Queues)
- Azure SQL 数据库
- Cosmos DB
- Service Bus
- Event Hubs
- 以及更多…
成本效益: 使用消费计划,只需为执行时间付费,非常适合具有零星使用模式的 AI 助手场景。
企业级安全性: 内置身份验证、网络隔离和托管标识支持,确保你的 AI 工具默认安全。
6、开始使用

1. 克隆示例存储库:
git clone https://github.com/Azure-Samples/remote-mcp-functions-typescript
git clone https://github.com/Azure-Samples/remote-mcp-functions-python
git clone https://github.com/Azure-Samples/remote-mcp-functions-java
git clone https://github.com/Azure-Samples/remote-mcp-functions-dotnet
cd remote-mcp-functions-typescript
2. 安装依赖:
npm install
3. 启动本地开发:
func start
4. 部署到 Azure:
azd up
7、结论

Azure Functions 中的远程 MCP(也称为 MCP 工具触发器)功能是构建云原生 AI 助手/工作流的绝佳机会。无服务器计算与 Azure 广泛的生态系统无缝集成,帮助开发人员创建强大、可扩展且经济的 AI 工具。
MCP 工具触发器也是文档管理系统、数据处理管道或自定义业务逻辑工作流中 AI 决策的捷径。与 Azure Functions 绑定系统的功能集成让你可以连接到几乎任何服务或数据源,从而增强 AI 助手的功能和实用性。
成为第一个使用指纹文件触发器的人,探索真正自主的云如何改变你的应用程序!
8、资源
- Azure Functions 文档
- 模型上下文协议规范
- 示例存储库 TypeScript
- 示例存储库 Python
- 示例存储库 DotNet
- 示例存储库 Java
- Azure Functions TypeScript 指南
生如逆旅,一苇以航
欢迎关注、欢迎联系交流、欢迎沟通想法、欢迎交换意见、欢迎合作咨询
感谢亲的关注、点赞、收藏、评论,一键三连支持,谢谢

更多推荐



所有评论(0)