adcp 1.0.3__tar.gz → 1.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {adcp-1.0.3/src/adcp.egg-info → adcp-1.0.4}/PKG-INFO +1 -1
  2. {adcp-1.0.3 → adcp-1.0.4}/pyproject.toml +1 -1
  3. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/__init__.py +1 -1
  4. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/protocols/mcp.py +39 -2
  5. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/utils/response_parser.py +4 -1
  6. {adcp-1.0.3 → adcp-1.0.4/src/adcp.egg-info}/PKG-INFO +1 -1
  7. {adcp-1.0.3 → adcp-1.0.4}/tests/test_protocols.py +56 -0
  8. {adcp-1.0.3 → adcp-1.0.4}/LICENSE +0 -0
  9. {adcp-1.0.3 → adcp-1.0.4}/README.md +0 -0
  10. {adcp-1.0.3 → adcp-1.0.4}/setup.cfg +0 -0
  11. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/__main__.py +0 -0
  12. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/client.py +0 -0
  13. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/config.py +0 -0
  14. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/exceptions.py +0 -0
  15. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/protocols/__init__.py +0 -0
  16. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/protocols/a2a.py +0 -0
  17. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/protocols/base.py +0 -0
  18. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/types/__init__.py +0 -0
  19. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/types/core.py +0 -0
  20. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/types/generated.py +0 -0
  21. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/types/tasks.py +0 -0
  22. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/utils/__init__.py +0 -0
  23. {adcp-1.0.3 → adcp-1.0.4}/src/adcp/utils/operation_id.py +0 -0
  24. {adcp-1.0.3 → adcp-1.0.4}/src/adcp.egg-info/SOURCES.txt +0 -0
  25. {adcp-1.0.3 → adcp-1.0.4}/src/adcp.egg-info/dependency_links.txt +0 -0
  26. {adcp-1.0.3 → adcp-1.0.4}/src/adcp.egg-info/entry_points.txt +0 -0
  27. {adcp-1.0.3 → adcp-1.0.4}/src/adcp.egg-info/requires.txt +0 -0
  28. {adcp-1.0.3 → adcp-1.0.4}/src/adcp.egg-info/top_level.txt +0 -0
  29. {adcp-1.0.3 → adcp-1.0.4}/tests/test_cli.py +0 -0
  30. {adcp-1.0.3 → adcp-1.0.4}/tests/test_client.py +0 -0
  31. {adcp-1.0.3 → adcp-1.0.4}/tests/test_code_generation.py +0 -0
  32. {adcp-1.0.3 → adcp-1.0.4}/tests/test_format_id_validation.py +0 -0
  33. {adcp-1.0.3 → adcp-1.0.4}/tests/test_response_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: Official Python client for the Ad Context Protocol (AdCP)
5
5
  Author-email: AdCP Community <maintainers@adcontextprotocol.org>
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "adcp"
7
- version = "1.0.3"
7
+ version = "1.0.4"
8
8
  description = "Official Python client for the Ad Context Protocol (AdCP)"
9
9
  authors = [
10
10
  {name = "AdCP Community", email = "maintainers@adcontextprotocol.org"}
@@ -46,7 +46,7 @@ from adcp.types.generated import (
46
46
  UpdateMediaBuyResponse,
47
47
  )
48
48
 
49
- __version__ = "1.0.3"
49
+ __version__ = "1.0.4"
50
50
 
51
51
  __all__ = [
52
52
  # Client classes
@@ -186,6 +186,40 @@ class MCPAdapter(ProtocolAdapter):
186
186
  else:
187
187
  raise ValueError(f"Unsupported transport scheme: {parsed.scheme}")
188
188
 
189
+ def _serialize_mcp_content(self, content: list[Any]) -> list[dict[str, Any]]:
190
+ """
191
+ Convert MCP SDK content objects to plain dicts.
192
+
193
+ The MCP SDK returns Pydantic objects (TextContent, ImageContent, etc.)
194
+ but the rest of the ADCP client expects protocol-agnostic dicts.
195
+ This method handles the translation at the protocol boundary.
196
+
197
+ Args:
198
+ content: List of MCP content items (may be dicts or Pydantic objects)
199
+
200
+ Returns:
201
+ List of plain dicts representing the content
202
+ """
203
+ result = []
204
+ for item in content:
205
+ # Already a dict, pass through
206
+ if isinstance(item, dict):
207
+ result.append(item)
208
+ # Pydantic v2 model with model_dump()
209
+ elif hasattr(item, "model_dump"):
210
+ result.append(item.model_dump())
211
+ # Pydantic v1 model with dict()
212
+ elif hasattr(item, "dict") and callable(item.dict):
213
+ result.append(item.dict())
214
+ # Fallback: try to access __dict__
215
+ elif hasattr(item, "__dict__"):
216
+ result.append(dict(item.__dict__))
217
+ # Last resort: serialize as unknown type
218
+ else:
219
+ logger.warning(f"Unknown MCP content type: {type(item)}, serializing as string")
220
+ result.append({"type": "unknown", "data": str(item)})
221
+ return result
222
+
189
223
  async def _call_mcp_tool(self, tool_name: str, params: dict[str, Any]) -> TaskResult[Any]:
190
224
  """Call a tool using MCP protocol."""
191
225
  start_time = time.time() if self.agent_config.debug else None
@@ -205,12 +239,15 @@ class MCPAdapter(ProtocolAdapter):
205
239
  # Call the tool using MCP client session
206
240
  result = await session.call_tool(tool_name, params)
207
241
 
242
+ # Serialize MCP SDK types to plain dicts at protocol boundary
243
+ serialized_content = self._serialize_mcp_content(result.content)
244
+
208
245
  if self.agent_config.debug and start_time:
209
246
  duration_ms = (time.time() - start_time) * 1000
210
247
  debug_info = DebugInfo(
211
248
  request=debug_request,
212
249
  response={
213
- "content": result.content,
250
+ "content": serialized_content,
214
251
  "is_error": result.isError if hasattr(result, "isError") else False,
215
252
  },
216
253
  duration_ms=duration_ms,
@@ -220,7 +257,7 @@ class MCPAdapter(ProtocolAdapter):
220
257
  # For AdCP, we expect the data in the content
221
258
  return TaskResult[Any](
222
259
  status=TaskStatus.COMPLETED,
223
- data=result.content,
260
+ data=serialized_content,
224
261
  success=True,
225
262
  debug_info=debug_info,
226
263
  )
@@ -20,10 +20,13 @@ def parse_mcp_content(content: list[dict[str, Any]], response_type: type[T]) ->
20
20
  MCP tools return content as a list of content items:
21
21
  [{"type": "text", "text": "..."}, {"type": "resource", ...}]
22
22
 
23
+ The MCP adapter is responsible for serializing MCP SDK Pydantic objects
24
+ to plain dicts before calling this function.
25
+
23
26
  For AdCP, we expect JSON data in text content items.
24
27
 
25
28
  Args:
26
- content: MCP content array
29
+ content: MCP content array (list of plain dicts)
27
30
  response_type: Expected Pydantic model type
28
31
 
29
32
  Returns:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: Official Python client for the Ad Context Protocol (AdCP)
5
5
  Author-email: AdCP Community <maintainers@adcontextprotocol.org>
6
6
  License: Apache-2.0
@@ -240,3 +240,59 @@ class TestMCPAdapter:
240
240
  mock_exit_stack.aclose.assert_called_once()
241
241
  assert adapter._exit_stack is None
242
242
  assert adapter._session is None
243
+
244
+ def test_serialize_mcp_content_with_dicts(self, mcp_config):
245
+ """Test serializing MCP content that's already dicts."""
246
+ adapter = MCPAdapter(mcp_config)
247
+
248
+ content = [
249
+ {"type": "text", "text": "Hello"},
250
+ {"type": "resource", "uri": "file://test.txt"},
251
+ ]
252
+
253
+ result = adapter._serialize_mcp_content(content)
254
+
255
+ assert result == content # Pass through unchanged
256
+ assert len(result) == 2
257
+
258
+ def test_serialize_mcp_content_with_pydantic_v2(self, mcp_config):
259
+ """Test serializing MCP content with Pydantic v2 objects."""
260
+ from pydantic import BaseModel
261
+
262
+ adapter = MCPAdapter(mcp_config)
263
+
264
+ class MockTextContent(BaseModel):
265
+ type: str
266
+ text: str
267
+
268
+ content = [
269
+ MockTextContent(type="text", text="Pydantic v2"),
270
+ ]
271
+
272
+ result = adapter._serialize_mcp_content(content)
273
+
274
+ assert len(result) == 1
275
+ assert result[0] == {"type": "text", "text": "Pydantic v2"}
276
+ assert isinstance(result[0], dict)
277
+
278
+ def test_serialize_mcp_content_mixed(self, mcp_config):
279
+ """Test serializing mixed MCP content (dicts and Pydantic objects)."""
280
+ from pydantic import BaseModel
281
+
282
+ adapter = MCPAdapter(mcp_config)
283
+
284
+ class MockTextContent(BaseModel):
285
+ type: str
286
+ text: str
287
+
288
+ content = [
289
+ {"type": "text", "text": "Plain dict"},
290
+ MockTextContent(type="text", text="Pydantic object"),
291
+ ]
292
+
293
+ result = adapter._serialize_mcp_content(content)
294
+
295
+ assert len(result) == 2
296
+ assert result[0] == {"type": "text", "text": "Plain dict"}
297
+ assert result[1] == {"type": "text", "text": "Pydantic object"}
298
+ assert all(isinstance(item, dict) for item in result)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes