8000 feat: support audio content (#725) · Perlic/python-sdk@7123556 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7123556

Browse files
feat: support audio content (modelcontextprotocol#725)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
1 parent 2bce10b commit 7123556

File tree

9 files changed

+60
-19
lines changed

9 files changed

+60
-19
lines changed

examples/servers/simple-streamablehttp-stateless/mcp_simple_streamablehttp_stateless/server.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ def main(
4343
@app.call_tool()
4444
async def call_tool(
4545
name: str, arguments: dict
46-
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
46+
) -> list[
47+
types.TextContent
48+
| types.ImageContent
49+
| types.AudioContent
50+
| types.EmbeddedResource
51+
]:
4752
ctx = app.request_context
4853
interval = arguments.get("interval", 1.0)
4954
count = arguments.get("count", 5)

examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ def main(
4747
@app.call_tool()
4848
async def call_tool(
4949
name: str, arguments: dict
50-
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
50+
) -> list[
51+
types.TextContent
52+
| types.ImageContent
53+
| types.AudioContent
54+
| types.EmbeddedResource
55+
]:
5156
ctx = app.request_context
5257
interval = arguments.get("interval", 1.0)
5358
count = arguments.get("count", 5)

examples/servers/simple-tool/mcp_simple_tool/server.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
async def fetch_website(
99
url: str,
10-
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
10+
) -> list[
11+
types.TextContent | types.ImageContent | types.AudioContent | types.EmbeddedResource
12+
]:
1113
headers = {
1214
"User-Agent": "MCP Test Server (github.com/modelcontextprotocol/python-sdk)"
1315
}
@@ -31,7 +33,12 @@ def main(port: int, transport: str) -> int:
3133
@app.call_tool()
3234
async def fetch_tool(
3335
name: str, arguments: dict
34-
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
36+
) -> list[
37+
types.TextContent
38+
| types.ImageContent
39+
| types.AudioContent
40+
| types.EmbeddedResource
41+
]:
3542
if name != "fetch":
3643
raise ValueError(f"Unknown tool: {name}")
3744
if "url" not in arguments:

src/mcp/server/fastmcp/prompts/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
import pydantic_core
88
from pydantic import BaseModel, Field, TypeAdapter, validate_call
99

10-
from mcp.types import EmbeddedResource, ImageContent, TextContent
10+
from mcp.types import AudioContent, EmbeddedResource, ImageContent, TextContent
1111

12-
CONTENT_TYPES = TextContent | ImageContent | EmbeddedResource
12+
CONTENT_TYPES = TextContent | ImageContent | AudioContent | EmbeddedResource
1313

1414

1515
class Message(BaseModel):

src/mcp/server/fastmcp/server.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
from mcp.shared.context import LifespanContextT, RequestContext, RequestT
5353
from mcp.types import (
5454
AnyFunction,
55+
AudioContent,
5556
EmbeddedResource,
5657
GetPromptResult,
5758
ImageContent,
@@ -275,7 +276,7 @@ def get_context(self) -> Context[ServerSession, object, Request]:
275276

276277
async def call_tool(
277278
self, name: str, arguments: dict[str, Any]
278-
) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
279+
) -> Sequence[TextContent | ImageContent | AudioContent | EmbeddedResource]:
279280
"""Call a tool by name with F438 arguments."""
280281
context = self.get_context()
281282
result = await self._tool_manager.call_tool(name, arguments, context=context)
@@ -875,12 +876,12 @@ async def get_prompt(
875876

876877
def _convert_to_content(
877878
result: Any,
878-
) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
879+
) -> Sequence[TextContent | ImageContent | AudioContent | EmbeddedResource]:
879880
"""Convert a result to a sequence of content objects."""
880881
if result is None:
881882
return []
882883

883-
if isinstance(result, TextContent | ImageContent | EmbeddedResource):
884+
if isinstance(result, TextContent | ImageContent | AudioContent | EmbeddedResource):
884885
return [result]
885886

886887
if isinstance(result, Image):

src/mcp/server/lowlevel/server.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,10 @@ def decorator(
405405
...,
406406
Awaitable[
407407
Iterable[
408-
types.TextContent | types.ImageContent | types.EmbeddedResource
408+
types.TextContent
409+
| types.ImageContent
410+
| types.AudioContent
411+
| types.EmbeddedResource
409412
]
410413
],
411414
],

src/mcp/types.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -657,11 +657,26 @@ class ImageContent(BaseModel):
657657
model_config = ConfigDict(extra="allow")
658658

659659

660+
class AudioContent(BaseModel):
661+
"""Audio content for a message."""
662+
663+
type: Literal["audio"]
664+
data: str
665+
"""The base64-encoded audio data."""
666+
mimeType: str
667+
"""
668+
The MIME type of the audio. Different providers may support different
669+
audio types.
670+
"""
671+
annotations: Annotations | None = None
672+
model_config = ConfigDict(extra="allow")
673+
674+
660675
class SamplingMessage(BaseModel):
661676
"""Describes a message issued to or received from an LLM API."""
662677

663678
role: Role
664-
content: TextContent | ImageContent
679+
content: TextContent | ImageContent | AudioContent
665680
model_config = ConfigDict(extra="allow")
666681

667682

@@ -683,7 +698,7 @@ class PromptMessage(BaseModel):
683698
"""Describes a message returned as part of a prompt."""
684699

685700
role: Role
686-
content: TextContent | ImageContent | EmbeddedResource
701+
content: TextContent | ImageContent | AudioContent | EmbeddedResource
687702
model_config = ConfigDict(extra="allow")
688703

689704

@@ -801,7 +816,7 @@ class CallToolRequest(Request[CallToolRequestParams, Literal["tools/call"]]):
801816
class CallToolResult(Result):
802817
"""The server's response to a tool call."""
803818

804-
content: list[TextContent | ImageContent | EmbeddedResource]
819+
content: list[TextContent | ImageContent | AudioContent | EmbeddedResource]
805820
isError: bool = False
806821

807822

@@ -965,7 +980,7 @@ class CreateMessageResult(Result):
965980
"""The client's response to a sampling/create_message request from the server."""
966981

967982
role: Role
968-
content: TextContent | ImageContent
983+
content: TextContent | ImageContent | AudioContent
969984
model: str
970985
"""The name of the model that generated the message."""
971986
stopReason: StopReason | None = None

tests/issues/test_88_random_error.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from mcp.server.lowlevel import Server
1313
from mcp.shared.exceptions import McpError
1414
from mcp.types import (
15+
AudioContent,
1516
EmbeddedResource,
1617
ImageContent,
1718
TextContent,
@@ -37,7 +38,7 @@ async def test_notification_validation_error(tmp_path: Path):
3738
@server.call_tool()
3839
async def slow_tool(
3940
name: str, arg
40-
) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
41+
) -> Sequence[TextContent | ImageContent | AudioContent | EmbeddedResource]:
4142
nonlocal request_count
4243
request_count += 1
4344

tests/server/fastmcp/test_server.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
create_connected_server_and_client_session as client_session,
1717
)
1818
from mcp.types import (
19+
AudioContent,
1920
BlobResourceContents,
2021
ImageContent,
2122
TextContent,
@@ -207,10 +208,11 @@ def image_tool_fn(path: str) -> Image:
207208
return Image(path)
208209

209210

210-
def mixed_content_tool_fn() -> list[TextContent | ImageContent]:
211+
def mixed_content_tool_fn() -> list[TextContent | ImageContent | AudioContent]:
211212
return [
212213
TextContent(type="text", text="Hello"),
213214
ImageContent(type="image", data="abc", mimeType="image/png"),
215+
AudioContent(type="audio", data="def", mimeType="audio/wav"),
214216
]
215217

216218

@@ -312,14 +314,16 @@ async def test_tool_mixed_content(self):
312314
mcp.add_tool(mixed_content_tool_fn)
313315
async with client_session(mcp._mcp_server) as client:
314316
result = await client.call_tool("mixed_content_tool_fn", {})
315-
assert len(result.content) == 2
316-
content1 = result.content[0]
317-
content2 = result.content[1]
317+
assert len(result.content) == 3
318+
content1, content2, content3 = result.content
318319
assert isinstance(content1, TextContent)
319320
assert content1.text == "Hello"
320321
assert isinstance(content2, ImageContent)
321322
assert content2.mimeType == "image/png"
322323
assert content2.data == "abc"
324+
assert isinstance(content3, AudioContent)
325+
assert content3.mimeType == "audio/wav"
326+
assert content3.data == "def"
323327

324328
@pytest.mark.anyio
325329
async def test_tool_mixed_list_with_image(self, tmp_path: Path):

0 commit comments

Comments
 (0)
0