这篇适合谁、读完能做到什么
适合想让 MCP 工具处理图片、音频,或者让模型返回生成图片的开发者。读完你能设计多模态工具的输入和输出,看懂示例里图片分析、音频转写和图像生成三类工具的写法,并知道哪些地方只是占位,不能直接上线。
多模态 MCP 通常由哪几部分组成
多模态实现一般包括四个部分[1]:
- 模态解析器:把不同类型的媒体转换成模型能处理的格式[1]。
- 模态工具:专门处理某一种模态的工具,例如图像分析或音频处理[1]。
- 统一的上下文管理:在不同模态之间保持上下文[1]。
- 响应生成:生成可能同时包含多种模态的回复[1]。
上下文管理在图文对话里最容易被忽视。用户先发一张图,再问一个文字问题,第二轮回答仍然需要引用第一轮的图。如果每个工具各自处理输入、互不知情,这种引用就会断掉。
图片分析:工具怎么收参数、怎么回结果
C# 示例定义了一个名为 imageAnalysis 的工具。它的必填参数是 imageUrl,可选参数是 analysisType,取值为 general、objects、text 或 faces,默认是 general[1]。示例里的 IImageAnalysisService 是假设的服务,要接入真实的图像识别能力,需要自己实现它[1]。
执行流程分三步:下载图片,按 analysisType 分派到对应的方法,再把结果序列化成 JSON[1]:
public async Task<ToolResponse> ExecuteAsync(IDictionary<string, object> parameters)
{
// Extract parameters
string imageUrl = parameters["imageUrl"].ToString();
string analysisType = parameters.ContainsKey("analysisType")
? parameters["analysisType"].ToString()
: "general";
// Download or access the image
byte[] imageData = await DownloadImageAsync(imageUrl);
// Analyze based on the requested analysis type
var analysisResult = analysisType switch
{
"objects" => await _imageService.DetectObjectsAsync(imageData), "text" => await _imageService.RecognizeTextAsync(imageData),
"faces" => await _imageService.DetectFacesAsync(imageData),
_ => await _imageService.AnalyzeGeneralAsync(imageData) // Default general analysis
};这里有一个设计上的选择需要注意。如果以后要让客户端直接在图片上画出框或标注,就需要返回结构化的数据,而示例现在只返回一段 JSON 文本。分析结果是以 Text 类型的内容项返回的 JSON 字符串,而不是图片或结构化的内容类型[1]。只需要文字结果的客户端用起来没有问题;如果客户端期望结构化数据,就要自己解析这段 JSON。
示例把服务器的最大请求体设为 10MB,并列出支持的内容类型:image/jpeg、image/png、text/plain 和 application/json[1]。请求体超过这个上限时,应当在入口处拦下,而不是交给工具去处理。
音频转写:URL 和 Base64 二选一
这一节的工具是一个典型的「两种输入、一个出口」设计:不管调用方给的是地址还是数据,后面的转写逻辑都只看到字节数组。把输入来源的差异收在工具内部,是这种写法的好处。
Java 示例实现了一个名为 audioTranscription 的工具。它接受 audioUrl 或 Base64 编码的 audioData 两种输入之一,language 参数默认为 en-US[1]。执行时先检查 audioUrl,没有再检查 audioData;两者都没有,就抛出 "Either audioUrl or audioData must be provided"[1]:
public ToolResponse execute(ToolRequest request) {
try {
byte[] audioData;
String language = request.getParameters().has("language") ?
request.getParameters().get("language").asText() : "en-US";
// Get audio either from URL or direct data
if (request.getParameters().has("audioUrl")) {
String audioUrl = request.getParameters().get("audioUrl").asText();
audioData = downloadAudio(audioUrl);
} else if (request.getParameters().has("audioData")) {
String base64Audio = request.getParameters().get("audioData").asText();
audioData = Base64.getDecoder().decode(base64Audio);
} else {
throw new ToolExecutionException("Either audioUrl or audioData must be provided");
}这段代码有两个需要先处理的地方。第一,downloadAudio 目前只是占位,直接返回一个空的字节数组,所以 URL 这条分支还没有实际功能[1]。第二,getSchema 里用了 Arrays.asList,但示例的导入中没有 java.util.Arrays,编译前要补上这一行[1]。
服务器的最大请求体在这里设为 20MB,示例注释说明这是为音频和视频内容留的空间[1]。音频文件通常比文本大得多,上限要和实际文件大小对得上。另外,转写结果来自 audioProcessor.transcribe 返回的映射对象,示例没有规定它的字段名。调用方要和服务端事先约定字段,否则只能靠猜。
图像生成:返回 Base64 编码的 PNG
把图片直接放进 JSON 返回,好处是调用方拿到结果就能用,不必再去另一个地址下载。代价是结果体积大,每次都要整段传输和解码。示例选择了这种做法,所以生成的图片越大,响应也越大。
Python 示例的 imageGeneration 工具接收 prompt(必填)、style(取值 realistic、artistic、cartoon、sketch,默认 realistic)、width 和 height(默认都是 512)[1]。生成后,工具把图片编码为 Base64 PNG,连同 format、width、height、generationPrompt 和 style 一起放进结果对象返回[1]。
真正的生成逻辑在 _generate_image 里,而示例中它只是占位:画一张纯色图片。注释写明,这里应当调用真实的图像生成 API[1]:
async def _generate_image(self, prompt: str, style: str, width: int, height: int) -> Image.Image:
"""
This would call an actual image generation API
Simplified placeholder implementation
"""
# Return a placeholder image or call actual image generation API
# For this example, we'll create a simple colored image
image = Image.new('RGB', (width, height), color=(73, 109, 137))
return image多模态回复由 MultiModalResponseHandler 组织。generate_images 为真时,它会对每个提示词调用一次 imageGeneration,把 Base64 数据收进回复的 images 列表[1]:
if generate_images and image_prompts:
for prompt in image_prompts:
image_result = await self.client.execute_tool(
"imageGeneration",
{
"prompt": prompt,
"style": "realistic",
"width": 512,
"height": 512
}
)
response["images"].append({
"imageData": image_result.result["imageBase64"],
"format": image_result.result["format"],
"prompt": prompt
})main 函数还注册了 AudioAnalysisTool 和 VideoFrameExtractionTool,但示例中没有定义这两个类[1]。照抄整段代码会直接报错,需要自己补上实现。
输入用 URL 还是 Base64
同一类图片或音频,示例里有两种传法,选哪种要看场景。
用 URL 时,服务器自己去取文件。请求本身很小,但服务器必须能访问这个地址,地址失效或需要登录时,工具就会在下载这一步失败。这是为什么 C# 示例先调用 DownloadImageAsync,而且没有对失败做额外处理。
用 Base64 时,文件内容直接放在请求里。Base64 把每三个字节编码成四个字符,体积会增加约三分之一,所以大文件很快就会撞上请求体的上限。Java 示例把两种方式并列放在同一个工具里,正是为了让调用方按场景选择[1]。
设计多模态工具的几点检查
写一个新的多模态工具之前,可以对照示例过一遍下面几点:
- 输入的模态和格式要写进参数定义。C# 示例在服务器选项里列出了允许的内容类型,客户端就知道能传什么[1]。
- 请求体上限要和实际文件大小对得上,超过上限的请求应当在入口处拒绝[1]。
- 输出用什么内容类型要想清楚。结果是纯文本、结构化 JSON,还是 Base64 图片,决定了客户端需要多少解码和展示工作。
- 错误信息要写具体。Java 示例在两个输入都缺失时给出明确的提示,比笼统的「执行失败」有用得多[1]。
- 占位代码要显眼。示例里的
downloadAudio、_generate_image都是占位,上线前必须替换,否则功能看起来存在,实际上什么也没做[1]。
常见问题
- 客户端看不到图片:三个示例返回的都是文字或 Base64 字符串,不是图片内容类型。客户端需要自己解码并显示。
- 生成的图片全是同一种颜色:这是占位实现,不是生成失败。占位图的颜色是示例里写死的 (73, 109, 137),换一个颜色也不会改变它是占位的事实。接入真实的生成服务后,结果才会有实际内容[1]。
- 音频工具返回的内容是空的:
downloadAudio目前返回空字节数组[1],问题在占位代码,而不是转写服务。 - 编译时报找不到
Arrays:补上java.util.Arrays的导入[1]。 - 请求被拒绝:检查请求体大小是否超过服务器设置的上限[1]。
下一步
接下来可以读 OAuth 2 相关的内容。多模态工具一旦接入外部的图像或语音服务,鉴权就绕不开了[1]。这一点要在设计时就考虑清楚,而不是上线前才补。
本文改编自 Microsoft 开源课程《MCP for Beginners》中关于多模态集成的高级主题[1],原文以 MIT 许可证发布。
来源
- 1Multi-Modal IntegrationMicrosoft mcp-for-beginners (MIT)
引用本页: MCP 处理图片、音频等多模态内容. TellWorld, https://www.tellworld.net/zh/practice/practice-mcp-multi-modality. 更新于 2026-11-14