According to the MCP spec and FastMCP documentation, the meta parameter is attached to the resource definition, and is supposed to appear when listing resources (resources/list or templates/list). It is not included in the resources/read response, which only returns the file contents.
However, the problem is:
When the OpenAI client calls mcp.read, it expects the meta field to be present in the response.
Because FastMCP does not return meta in read, the OpenAI client reports a missing field or does not behave as expected.
This makes it impossible to correctly implement certain MCP resources (for example, widget resources) using FastMCP, because the OpenAI client assumes a meta field that FastMCP will never include in the read response.
It seems like:
FastMCP is following the MCP spec correctly
But the OpenAI client is expecting behavior that doesn’t match the spec (or at least isn’t supported by FastMCP)
So right now, there is no way to satisfy the OpenAI client’s expectations using FastMCP, unless I manually inject meta information into the resource contents, which breaks the intended protocol.
Could the OpenAI team clarify whether:
The meta field should be included in resources/read responses (meaning FastMCP needs an update), or
The OpenAI client should not expect meta from read, and the current behavior is a bug?
This mismatch currently prevents proper widget integration when using FastMCP (CSP Issue)
It’s very messy, but in their Python FastMCP examples on Github it looks like they override the normal FastMCP behaviour with custom handlers and include _meta in the response.
This is similar to what I’m planning to do, but going to keep the original handler call to hopefully future proof it a little bit (really don’y like overriding these private methods when FastMCP is moving so fast!).
Override this method on a subclassed FastMCP:
def _setup_read_resource_handler(self) -> None:
"""
Override to include resource meta in read_resource responses.
og
https://github.com/jlowin/fastmcp/blob/2f561ec98d0b5f078221acf6a2126b1152966365/src/fastmcp/server/server.py#L679
"""
super()._setup_read_resource_handler()
original_handler = self._mcp_server.request_handlers[ReadResourceRequest]
async def wrapper(req: ReadResourceRequest) -> ServerResult:
uri = req.params.uri
# Look up resource first to get meta
resource = None
resource_meta = None
async with Context(fastmcp=self):
resource = await self._get_resource_or_template_or_none(str(uri))
if resource and self._should_enable_component(resource):
resource_meta = resource.get_meta(
include_fastmcp_meta=self.include_fastmcp_meta
)
# Use parent handler for task routing and reading
result = await original_handler(req)
# Rebuild contents with meta (Pydantic models are immutable)
read_result = result.root
if (
resource_meta
and isinstance(read_result, ReadResourceResult)
and read_result.contents
):
new_contents = []
for content in read_result.contents:
existing_meta = getattr(content, "_meta", None) or {}
merged_meta = {**resource_meta, **existing_meta}
if hasattr(content, "text"):
new_contents.append(
TextResourceContents(
uri=content.uri,
text=content.text,
mimeType=content.mimeType,
_meta=merged_meta,
)
)
elif hasattr(content, "blob"):
new_contents.append(
BlobResourceContents(
uri=content.uri,
blob=content.blob,
mimeType=content.mimeType,
_meta=merged_meta,
)
)
else:
new_contents.append(content)
return ServerResult(ReadResourceResult(contents=new_contents))
return result
self._mcp_server.request_handlers[ReadResourceRequest] = wrapper