feat(mcp): add resource read tools (#33483)
This commit is contained in:
@@ -66,6 +66,14 @@ globalThis.AI_SDK_LOG_WARNINGS = false
|
||||
|
||||
const decodeMessageInfo = Schema.decodeUnknownExit(SessionV1.Info)
|
||||
const decodeMessagePart = Schema.decodeUnknownExit(SessionV1.Part)
|
||||
const MAX_MCP_RESOURCE_BLOB_BYTES = 10 * 1024 * 1024
|
||||
const SUPPORTED_MCP_RESOURCE_ATTACHMENT_MIMES = new Set([
|
||||
"application/pdf",
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
])
|
||||
|
||||
const STRUCTURED_OUTPUT_DESCRIPTION = `Use this tool to return your final response in the requested structured format.
|
||||
|
||||
@@ -77,6 +85,18 @@ IMPORTANT:
|
||||
|
||||
const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested structured output. You MUST use the StructuredOutput tool to provide your final response. Do NOT respond with plain text - you MUST call the StructuredOutput tool with your answer formatted according to the schema.`
|
||||
|
||||
function mcpResourceBase64Size(value: string) {
|
||||
const trimmed = value.replace(/\s/g, "")
|
||||
const padding = trimmed.endsWith("==") ? 2 : trimmed.endsWith("=") ? 1 : 0
|
||||
return Math.max(0, Math.floor((trimmed.length * 3) / 4) - padding)
|
||||
}
|
||||
|
||||
function formatMcpResourceBytes(value: number) {
|
||||
if (value < 1024) return `${value} B`
|
||||
if (value < 1024 * 1024) return `${Math.ceil(value / 1024)} KB`
|
||||
return `${Math.ceil(value / (1024 * 1024))} MB`
|
||||
}
|
||||
|
||||
function isOrphanedInterruptedTool(part: SessionV1.ToolPart) {
|
||||
// cleanup() marks abandoned tool_use blocks this way after retries/aborts.
|
||||
// They are not pending work and must not trigger an assistant-prefill request.
|
||||
@@ -731,7 +751,8 @@ export const layer = Layer.effect(
|
||||
if (!content) throw new Error(`Resource not found: ${clientName}/${uri}`)
|
||||
const items = Array.isArray(content.contents) ? content.contents : [content.contents]
|
||||
for (const c of items) {
|
||||
if ("text" in c && c.text) {
|
||||
if (!c || typeof c !== "object") continue
|
||||
if ("text" in c && typeof c.text === "string" && c.text) {
|
||||
pieces.push({
|
||||
messageID: info.id,
|
||||
sessionID: input.sessionID,
|
||||
@@ -739,18 +760,47 @@ export const layer = Layer.effect(
|
||||
synthetic: true,
|
||||
text: c.text,
|
||||
})
|
||||
} else if ("blob" in c && c.blob) {
|
||||
const mime = "mimeType" in c ? c.mimeType : part.mime
|
||||
} else if ("blob" in c && typeof c.blob === "string" && c.blob) {
|
||||
const mime = "mimeType" in c && typeof c.mimeType === "string" ? c.mimeType : part.mime
|
||||
const filename = "uri" in c && typeof c.uri === "string" ? c.uri : part.filename
|
||||
const size = mcpResourceBase64Size(c.blob)
|
||||
if (!SUPPORTED_MCP_RESOURCE_ATTACHMENT_MIMES.has(mime)) {
|
||||
pieces.push({
|
||||
messageID: info.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
synthetic: true,
|
||||
text: `[Binary MCP resource omitted: ${filename ?? uri} (${mime}, ${formatMcpResourceBytes(size)}) is not a supported attachment type]`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if (size > MAX_MCP_RESOURCE_BLOB_BYTES) {
|
||||
pieces.push({
|
||||
messageID: info.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
synthetic: true,
|
||||
text: `[Binary MCP resource omitted: ${filename ?? uri} (${mime}, ${formatMcpResourceBytes(size)}) exceeds ${formatMcpResourceBytes(MAX_MCP_RESOURCE_BLOB_BYTES)}]`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
pieces.push({
|
||||
messageID: info.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
synthetic: true,
|
||||
text: `[Binary content: ${mime}]`,
|
||||
text: `[Binary MCP resource attached: ${filename ?? uri} (${mime})]`,
|
||||
})
|
||||
pieces.push({
|
||||
messageID: info.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "file",
|
||||
mime,
|
||||
filename,
|
||||
url: `data:${mime};base64,${c.blob}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
pieces.push({ ...part, messageID: info.id, sessionID: input.sessionID })
|
||||
} else {
|
||||
const error = Cause.squash(exit.cause)
|
||||
yield* Effect.logError("failed to read MCP resource", { error, clientName, uri })
|
||||
|
||||
Reference in New Issue
Block a user