尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于MCP协议构建AI Agent,实现M365 Copilot与Power Apps业务数据安全集成

基于MCP协议构建AI Agent,实现M365 Copilot与Power Apps业务数据安全集成 最近在尝试将企业内部的业务数据与AI助手如M365 Copilot进行深度集成时遇到了一个核心挑战如何让AI安全、可控地访问和操作像Power Apps这样的低代码平台生成的数据传统的API集成方式不仅开发周期长权限管理复杂而且难以让AI以自然语言的方式理解业务上下文。经过一番探索我发现MCPModel Context Protocol协议结合AI Agent工作流为这个问题提供了一个优雅的解决方案。本文将详细拆解如何利用MCP协议构建一个能够打通M365 Copilot与Power Apps业务数据的AI Agent实现“用对话驱动业务”的自动化场景。无论你是负责企业数字化转型的后端开发者还是对AI应用集成感兴趣的工程师本文都将提供一套从概念到落地的完整实操指南。你将掌握MCP的核心思想学会搭建一个基础的MCP Server来连接Power Apps并最终让M365 Copilot能够根据你的指令实时查询或更新业务数据。1. 背景与核心概念为什么需要MCP来连接AI与业务数据在深入技术细节之前我们首先要理解当前AI应用集成的痛点以及MCP协议试图解决的根源问题。传统集成方式的瓶颈假设你公司使用Power Apps构建了一个简单的员工信息管理应用或项目审批流程。当你想让M365 Copilot帮你“查看张三的剩余年假”或“批准编号为PRJ-2024-001的项目”时会面临以下问题权限与安全直接让Copilot访问数据库或后端API存在巨大的安全风险需要精细的权限控制和认证机制。上下文理解AI模型不理解“剩余年假”这个业务概念对应数据库中的哪个字段、哪张表以及如何计算。动作执行AI可以生成回答但很难直接触发一个审批流程或更新某个字段的状态。开发成本为每一个业务功能开发专用的Copilot插件或Graph API连接器工作量巨大。MCPModel Context Protocol是什么MCP是一个开放的协议它定义了大型语言模型LLM与外部工具、数据源和服务进行交互的标准方式。你可以把它想象成AI世界的“USB协议”或“驱动程序框架”。它的核心目标是让AI模型能够动态地发现、理解并安全地调用外部能力。通过MCP一个AI助手如Copilot不再是一个封闭的知识库而是一个可以通过标准化接口“插拔”各种专业工具的智能中枢。这些外部能力被封装成一个个“工具Tools”或“资源Resources”由MCP Server提供。AI Agent工作流在此场景下的角色AI Agent是一个能够感知环境、进行决策并执行动作以完成目标的自治程序。在我们的场景中这个“Agent”就是由M365 Copilot或类似的LLM扮演的“大脑”而MCP Server提供的工具则是它的“手”和“眼”。工作流如下用户向Copilot提出自然语言请求如“批准项目PRJ-2024-001”。CopilotAgent分析请求发现需要调用一个“审批项目”的工具。Copilot通过MCP协议向已注册的MCP Server发送结构化指令。MCP Server接收到指令将其转换为对Power Apps后端API的具体调用包括认证、参数组装等。MCP Server获取结果通过MCP协议返回给Copilot。Copilot将结果组织成自然语言回复给用户。Power Apps作为数据源Power Apps是微软的低代码平台其背后连接着Dataverse、SharePoint List、SQL数据库等多种数据源。它本身提供了丰富的REST API如Dataverse Web API。我们的MCP Server主要就是与这些API进行交互。2. 环境准备与版本说明在开始构建之前我们需要搭建开发环境。本文将使用Python作为开发MCP Server的主要语言因为它有丰富的库和相对简单的MCP协议实现。核心环境清单操作系统Windows 10/11, macOS, 或 Linux (WSL2 on Windows也可)。本文示例命令以macOS/Linux bash为主Windows用户可在PowerShell或WSL中操作。Python版本 3.9 或更高。推荐使用 3.10。检查命令python --version或python3 --version包管理工具pip(通常随Python安装)。代码编辑器VS Code (推荐有良好的Python和HTTP调试支持) 或 PyCharm。MCP SDK我们将使用mcp这个Python库来快速构建Server。这是目前社区较活跃的一个实现。微软身份认证库msal用于处理与Azure ADMicrosoft Entra ID的OAuth2认证这是调用Power Apps API所必需的。HTTP客户端库requests用于调用Power Apps的REST API。M365 Copilot 环境你需要一个拥有M365 Copilot许可的Microsoft 365租户并确保你有权限在该租户内测试AI助手集成。Power Apps 环境你需要一个Power Apps环境其中包含你想要访问的数据表例如Dataverse表。版本声明与依赖管理本文的重点是演示架构和核心代码逻辑因此不会锁定到某个特定的库小版本。实际开发中建议使用requirements.txt或pyproject.toml来管理依赖。一个示例的requirements.txt文件内容如下mcp0.1.0 msal1.24.0 requests2.31.0 python-dotenv1.0.0 # 用于管理环境变量 uvicorn0.24.0 # 可选用于运行ASGI Server你可以通过以下命令安装pip install -r requirements.txt3. MCP核心原理与协议拆解要构建MCP Server必须理解MCP协议的几个核心概念和交互模式。MCP通信通常基于标准输入输出stdio或HTTP本文我们将构建一个基于stdio的Server因为它更容易与各种AI客户端集成。核心交互模型MCP Server启动后会与客户端Client即AI模型运行时环境建立一个双向通信通道。通信的基本单位是JSON-RPC 2.0格式的消息。关键操作Operations初始化initialize客户端与服务器建立连接后首先交换能力信息。工具列表tools/list客户端查询服务器提供了哪些可用的工具。工具调用tools/call客户端请求服务器执行某个工具并传入参数。资源列表resources/list与资源读取resources/read客户端查询和读取服务器提供的静态资源如文档、模板。本文主要聚焦于“工具”。一个工具Tool的定义在MCP中一个工具需要明确描述其name唯一标识符如get_employee_leave。description给AI模型看的自然语言描述至关重要。例如“根据员工姓名或工号查询其剩余年假天数。需要‘员工标识’作为参数。”inputSchema定义工具接受的参数使用JSON Schema格式。这告诉AI模型应该如何构造请求。当AI模型如Copilot收到用户请求时它会根据工具的description和inputSchema来判断是否需要调用该工具并自动生成符合要求的参数。4. 完整实战构建连接Power Apps的MCP Server现在我们开始动手构建。我们的目标是创建一个MCP Server它至少提供两个工具1) 查询员工信息2) 更新项目审批状态。4.1 项目结构与初始化首先创建项目目录和文件。mkdir mcp-powerapps-agent cd mcp-powerapps-agent touch server.py touch .env touch requirements.txt将前面提到的依赖内容写入requirements.txt然后安装。 在.env文件中我们将存放敏感配置切勿提交到版本库# .env TENANT_IDyour-azure-ad-tenant-id CLIENT_IDyour-app-registration-client-id CLIENT_SECRETyour-app-registration-client-secret POWER_APPS_ENVIRONMENT_URLhttps://your-org.crm.dynamics.com/ DATAVERSE_TABLE_EMPLOYEEcrxxx_employees # 你的员工表逻辑名 DATAVERSE_TABLE_PROJECTcrxxx_projects # 你的项目表逻辑名4.2 实现Power Apps (Dataverse) API 客户端我们需要一个辅助类来处理与Dataverse API的认证和通信。在项目根目录创建dataverse_client.py。# dataverse_client.py import os import requests from msal import ConfidentialClientApplication from dotenv import load_dotenv load_dotenv() # 加载.env文件中的环境变量 class DataverseClient: def __init__(self): self.tenant_id os.getenv(TENANT_ID) self.client_id os.getenv(CLIENT_ID) self.client_secret os.getenv(CLIENT_SECRET) self.resource os.getenv(POWER_APPS_ENVIRONMENT_URL).rstrip(/) self.api_url f{self.resource}/api/data/v9.2/ self.authority fhttps://login.microsoftonline.com/{self.tenant_id} self.scope [f{self.resource}/.default] self.session None self._acquire_token() def _acquire_token(self): 获取访问Dataverse API所需的Bearer Token app ConfidentialClientApplication( self.client_id, authorityself.authority, client_credentialself.client_secret, ) result app.acquire_token_for_client(scopesself.scope) if access_token in result: self.session requests.Session() self.session.headers.update({ Authorization: fBearer {result[access_token]}, OData-MaxVersion: 4.0, OData-Version: 4.0, Accept: application/json, Content-Type: application/json; charsetutf-8 }) else: raise Exception(fCould not acquire token: {result.get(error_description)}) def get_entity(self, entity_name, entity_idNone, filtersNone, selectNone): 查询记录。可以按ID查单条或按过滤条件查多条。 url f{self.api_url}{entity_name} if entity_id: url f{url}({entity_id}) params {} if filters: params[$filter] filters if select: params[$select] select response self.session.get(url, paramsparams) response.raise_for_status() return response.json() def update_entity(self, entity_name, entity_id, update_data): 更新一条记录。 url f{self.api_url}{entity_name}({entity_id}) # Dataverse API 使用 PATCH 方法进行更新 response self.session.patch(url, jsonupdate_data) response.raise_for_status() return response.status_code 204 # 成功更新返回204 No Content # 单例模式方便在MCP Server中引用 client DataverseClient()关键点解释认证使用msal库通过客户端凭证Client Credentials流获取访问令牌。这种模式适用于服务器端对服务器的场景。在生产环境中你需要先在Azure Portal中注册一个应用并授予它访问对应Dataverse环境的API权限例如CommonDataService.ReadWrite.All。API端点Dataverse Web API的通用格式是{环境URL}/api/data/v9.2/。查询参数$filter用于过滤$select用于选择特定字段遵循OData v4协议。4.3 构建MCP Server主程序接下来是核心部分server.py。我们将使用mcp库来创建服务器。# server.py #!/usr/bin/env python3 import asyncio import json import sys from typing import Any from mcp import ClientSession, StdioServerParameters from mcp.server import Server from mcp.server.models import InitializationOptions import mcp.server.stdio from dataverse_client import client # 导入我们刚才写的客户端 from dotenv import load_dotenv load_dotenv() # 创建MCP Server实例 app Server(powerapps-mcp-server) # 1. 定义工具查询员工假期 app.list_tools() async def handle_list_tools() - list[dict[str, Any]]: return [ { name: get_employee_leave, description: 根据员工的姓名或工号查询其剩余的带薪年假天数。需要提供‘employee_identifier’参数可以是姓名或工号。, inputSchema: { type: object, properties: { employee_identifier: { type: string, description: 员工的姓名或工号例如‘张三’或‘EMP001’ } }, required: [employee_identifier] } }, { name: update_project_status, description: 更新指定项目的审批状态。需要提供‘project_number’项目编号和‘new_status’新状态如‘已批准’、‘已拒绝’、‘进行中’。, inputSchema: { type: object, properties: { project_number: { type: string, description: 项目的唯一编号例如‘PRJ-2024-001’ }, new_status: { type: string, description: 要更新为的状态必须是预定义值之一。, enum: [已批准, 已拒绝, 进行中, 已完成] } }, required: [project_number, new_status] } } ] # 2. 实现工具调用逻辑 app.call_tool() async def handle_call_tool(name: str, arguments: dict[str, Any]) - dict[str, Any]: try: if name get_employee_leave: return await _handle_get_employee_leave(arguments) elif name update_project_status: return await _handle_update_project_status(arguments) else: raise ValueError(fUnknown tool: {name}) except Exception as e: # 将异常信息返回给客户端便于AI模型理解错误 return { content: [{ type: text, text: f调用工具‘{name}’时发生错误{str(e)} }] } async def _handle_get_employee_leave(arguments: dict) - dict[str, Any]: 处理查询员工假期的请求 identifier arguments[employee_identifier] entity_name os.getenv(DATAVERSE_TABLE_EMPLOYEE, crxxx_employees) # 构建查询过滤器假设我们有一个‘employee_id’字段和一个‘full_name’字段 filter_query fcontains(full_name, {identifier}) or employee_id eq {identifier} select_fields full_name, employee_id, annual_leave_balance response client.get_entity(entity_name, filtersfilter_query, selectselect_fields) # Dataverse API返回格式是 {“value”: [...]} employees response.get(value, []) if not employees: result_text f未找到标识为‘{identifier}’的员工。 else: emp employees[0] # 取第一个匹配结果 result_text f员工{emp.get(full_name)} (工号{emp.get(employee_id)})剩余年假{emp.get(annual_leave_balance)} 天。 # MCP协议要求返回特定格式的内容 return { content: [{ type: text, text: result_text }] } async def _handle_update_project_status(arguments: dict) - dict[str, Any]: 处理更新项目状态的请求 project_num arguments[project_number] new_status arguments[new_status] entity_name os.getenv(DATAVERSE_TABLE_PROJECT, crxxx_projects) # 1. 先根据项目编号找到对应的记录ID filter_query fproject_number eq {project_num} select_fields project_name, project_number, status, projectid response client.get_entity(entity_name, filtersfilter_query, selectselect_fields) projects response.get(value, []) if not projects: return { content: [{ type: text, text: f未找到编号为‘{project_num}’的项目。 }] } project projects[0] project_id project[projectid] # 2. 更新状态字段 (假设字段逻辑名为‘status’) update_payload { statuscode: _map_status_to_code(new_status) # 可能需要将中文状态映射为Dataverse选项集值 # 注意实际字段名和值需根据你的表结构调整。这里‘statuscode’是选项集字段。 } success client.update_entity(entity_name, project_id, update_payload) if success: result_text f项目‘{project.get(project_name)}’({project_num}) 状态已成功更新为‘{new_status}’。 else: result_text f更新项目状态失败请检查网络或权限。 return { content: [{ type: text, text: result_text }] } def _map_status_to_code(status_zh: str) - int: 一个简单的映射函数将中文状态映射为Dataverse选项集代码值。 实际值需要你在Power Apps中查看选项集的定义。 mapping { 已批准: 1, 已拒绝: 2, 进行中: 3, 已完成: 4, } return mapping.get(status_zh, 3) # 默认返回“进行中” # 3. 运行Server (基于stdio) async def main(): async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize(InitializationOptions(root_namepowerapps-tools, root_version0.1.0)) # 这里可以注册资源如果需要 # await session.list_resources() # 主循环等待客户端请求 await session.run() if __name__ __main__: asyncio.run(main())代码关键点解析工具定义handle_list_tools函数返回了两个工具的元数据。description字段写得非常详细这是为了“教育”AI模型何时以及如何使用这个工具。参数验证inputSchema定义了参数的类型、描述和是否必需。AI模型会据此生成调用参数。工具执行handle_call_tool是路由函数根据工具名调用对应的处理函数。所有处理函数都应是async的。业务逻辑在处理函数中我们使用DataverseClient来执行实际的API调用。注意错误处理任何异常都应被捕获并转化为AI可读的文本信息返回。状态映射_map_status_to_code函数演示了如何将自然语言状态如“已批准”映射到Dataverse表中选项集Option Set的实际整数值。这是实际集成中最容易出错的环节之一务必在Power Apps中确认字段的选项值。4.4 运行与测试MCP Server在连接Copilot之前我们需要先确保MCP Server本身能正常工作。我们可以使用一个简单的MCP客户端进行测试。首先确保你的.env文件配置正确并且你的Azure应用有访问Dataverse的权限。在一个终端运行Serverpython server.py此时Server会启动并等待通过stdio接收JSON-RPC消息。它不会输出任何内容因为它在等待客户端连接。我们需要另一个终端使用一个测试客户端。可以安装一个简单的MCP测试工具例如使用mcp库自带的客户端功能或者使用Node.js的modelcontextprotocol/sdk。这里我们用Python快速写一个测试脚本test_client.py# test_client.py import asyncio import json import subprocess import sys from mcp import ClientSession, StdioServerParameters async def test_tool_call(): # 配置Server进程参数 server_params StdioServerParameters( commandsys.executable, # Python解释器路径 args[server.py] # 我们的Server脚本 ) # 启动Server进程并建立会话 async with ClientSession(server_params) as session: await session.initialize() # 1. 列出所有工具 tools await session.list_tools() print(可用工具) for tool in tools: print(f - {tool.name}: {tool.description}) # 2. 测试调用‘get_employee_leave’工具 print(\n测试查询员工假期...) result await session.call_tool( get_employee_leave, arguments{employee_identifier: EMP001} # 替换为你的测试数据 ) for content in result.content: if content.type text: print(f结果{content.text}) # 3. 测试调用‘update_project_status’工具 print(\n测试更新项目状态...) result await session.call_tool( update_project_status, arguments{project_number: PRJ-2024-001, new_status: 已批准} ) for content in result.content: if content.type text: print(f结果{content.text}) if __name__ __main__: asyncio.run(test_tool_call())运行测试python test_client.py如果一切配置正确你应该能看到工具列表并收到来自Dataverse的真实数据或相应的错误信息。请务必先在Power Apps中创建测试数据员工表、项目表并确保API权限正确。4.5 与M365 Copilot或兼容客户端集成MCP是一个协议理论上任何支持该协议的客户端都可以连接我们的Server。目前M365 Copilot的原生MCP集成可能还在演进中。一种常见的集成模式是通过“AI Agent框架”作为桥梁。通用集成路径使用AI Agent框架许多AI Agent框架如LangChain, AutoGen, Dify等已经开始支持或计划支持MCP Server作为工具来源。你可以将上述MCP Server运行起来然后在Agent框架中配置其连接地址对于stdio Server框架需要以子进程方式启动它。框架连接Copilot这些框架再通过Microsoft Graph API或特定的Copilot SDK与M365 Copilot进行通信将用户的自然语言请求传递给框架中的AgentAgent再决定调用哪个MCP工具。直接与支持MCP的AI桌面客户端集成一些本地的AI应用如Claude Desktop, Cursor IDE等允许你配置自定义的MCP Server。你可以将Server配置到这些客户端中然后直接在这些客户端内使用自然语言操作Power Apps数据。以LangChain为例的伪代码思路# 伪代码展示概念 from langchain.agents import initialize_agent, AgentType from langchain.tools import Tool from langchain_openai import ChatOpenAI # 假设有一个LangChain的MCP集成库 from langchain_mcp_integration import MCPServerTool # 1. 将MCP Server封装为LangChain Tool mcp_tool MCPServerTool( server_command[python, server.py], # 或者 server_urlhttp://localhost:8080 (如果是HTTP Server) ) # 2. 初始化LLM (这里用OpenAI GPT模拟实际Copilot集成需用对应SDK) llm ChatOpenAI(modelgpt-4, temperature0) # 3. 创建Agent agent initialize_agent( tools[mcp_tool], llmllm, agentAgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, verboseTrue ) # 4. 运行 result agent.run(帮我查一下员工李四还剩多少天年假) print(result)在这个流程中LangChain Agent会分析问题从MCP Server提供的工具列表中选择get_employee_leave并自动将“李四”填充到employee_identifier参数中然后执行调用。5. 常见问题与排查思路在实际搭建和运行过程中你可能会遇到以下问题问题现象可能原因排查步骤与解决方案MCP Server启动失败或立即退出1. Python依赖未安装。2..env文件缺失或配置错误。3.mcp库版本不兼容。1. 运行pip install -r requirements.txt。2. 检查.env文件是否存在且变量名与代码中os.getenv()读取的一致。3. 查看mcp库的官方文档确认API用法。测试客户端报错“Connection refused”或超时Server未正确启动或stdio通信失败。1. 单独运行python server.py看是否有Python语法错误。2. 确保测试客户端和Server使用相同的MCP协议版本。3. 检查子进程启动命令是否正确。调用工具时返回认证错误1. Azure AD应用注册配置错误如重定向URI、权限。2. 客户端密钥过期。3. 应用未在Power Apps环境中获得同意。1. 在Azure Portal检查应用注册确保已添加CommonDataServiceAPI权限并已授予管理员同意。2. 生成新的客户端密钥并更新.env文件。3. 确保应用注册的“账户类型”支持你的租户如单租户、多租户。查询数据返回空列表或4041. Dataverse表逻辑名错误。2. 过滤条件语法错误或字段名不对。3. 应用对该表没有读取权限。1. 在Power Apps制作门户中打开表设置查看“逻辑名称”。2. 使用Postman或浏览器直接调用Dataverse API验证查询URL和过滤器格式。OData过滤器语法需准确。3. 在Azure AD中检查应用是否具有该表的相应安全角色。更新数据失败返回403或4001. 缺少写权限。2. 更新字段是只读的或类型不匹配。3. 选项集值映射错误。1. 同查询权限检查。2. 检查表字段的定义确认是否可以更新。使用PATCH方法。3.重点排查在Power Apps中查看选项集的“值”确保代码中的映射 (_map_status_to_code) 完全正确。AI模型不调用工具或调用参数错误1. 工具描述 (description) 不够清晰。2. 输入模式 (inputSchema) 定义模糊。3. AI模型本身能力限制。1. 优化description明确说明工具的用途、适用场景和参数含义。2. 细化inputSchema中的description和enum给AI模型更明确的指引。3. 尝试使用更强大的模型如GPT-4或在提示词中明确要求其使用工具。6. 最佳实践与工程建议将MCP用于生产环境需要考虑更多工程化因素安全至上最小权限原则为MCP Server使用的Azure AD应用分配尽可能小的权限例如只读权限优先于读写权限。机密管理永远不要将CLIENT_SECRET等硬编码在代码中。使用.env文件开发环境或Azure Key Vault、AWS Secrets Manager等专业服务生产环境。输入验证与清理在MCP Server的工具处理函数中对所有来自AI模型的输入参数进行严格的验证和清理防止注入攻击。尽管AI生成的参数通常较规范但仍需防范恶意提示或模型幻觉。错误处理与日志结构化日志在Server中集成如structlog或logging模块记录所有工具调用、参数、执行结果和错误。这对于调试和审计至关重要。友好的错误消息返回给AI模型的错误信息应清晰、可操作避免暴露内部细节如数据库错误堆栈。例如“未找到该员工请确认姓名或工号是否正确”比“SQL查询返回空”更好。性能与可扩展性连接池为Dataverse API客户端配置HTTP连接池避免频繁建立HTTPS连接的开销。异步优化确保MCP Server的异步处理逻辑 (async/await) 正确避免阻塞操作影响并发性能。工具粒度设计工具时粒度要适中。一个工具做一件事如“查询员工”和“更新状态”分开。避免创建“万能工具”这会让AI模型难以理解和正确调用。维护性配置化将工具的定义名称、描述、参数模式尽可能外置到配置文件如YAML中便于管理和扩展无需修改代码即可增加新工具。版本控制对MCP Server的接口进行版本管理。如果未来工具定义发生破坏性变更可以通过版本号让新旧客户端兼容。健康检查为MCP Server添加健康检查端点如果使用HTTP传输或信号便于监控系统感知其状态。与Power Apps的深度集成使用自定义API除了直接操作表Power Apps允许你创建“自定义API”Custom API。你可以将复杂的业务逻辑封装成自定义API然后让MCP Server去调用它。这比直接操作表更安全、更符合业务规范。利用环境变量在Power Apps中定义环境变量来存储配置如API端点提高MCP Server代码的可移植性。通过MCP协议将M365 Copilot与Power Apps等业务系统连接为我们打开了一扇新的大门让AI真正成为业务操作的智能界面。本文从概念到实践详细介绍了如何构建这样一个桥梁。虽然当前集成路径可能涉及多层MCP Server - Agent框架 - Copilot但随着MCP协议的普及和微软官方支持的加强未来的集成会越来越直接和顺畅。核心收获在于我们不再需要为每一个业务场景开发独立的AI插件而是通过一套标准化的协议MCP和一系列定义良好的工具让AI具备了动态扩展的能力。你可以基于这个模式继续为Copilot添加更多工具如“创建客户服务工单”、“生成销售报表”、“预订会议室”等逐步构建起一个强大、安全、可控的企业级AI助手生态。下一步你可以探索更复杂的工具定义、研究如何将MCP Server部署为常驻服务、或者尝试与其他支持MCP的AI平台如Claude, Gemini等进行集成。记住清晰的工具描述和稳健的API调用是成功的关键。
返回列表