adcp 1.4.0__tar.gz → 1.4.1__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 (41) hide show
  1. {adcp-1.4.0/src/adcp.egg-info → adcp-1.4.1}/PKG-INFO +1 -1
  2. {adcp-1.4.0 → adcp-1.4.1}/pyproject.toml +1 -1
  3. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/__init__.py +1 -1
  4. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/protocols/mcp.py +38 -16
  5. {adcp-1.4.0 → adcp-1.4.1/src/adcp.egg-info}/PKG-INFO +1 -1
  6. {adcp-1.4.0 → adcp-1.4.1}/tests/test_protocols.py +27 -3
  7. {adcp-1.4.0 → adcp-1.4.1}/LICENSE +0 -0
  8. {adcp-1.4.0 → adcp-1.4.1}/README.md +0 -0
  9. {adcp-1.4.0 → adcp-1.4.1}/setup.cfg +0 -0
  10. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/__main__.py +0 -0
  11. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/client.py +0 -0
  12. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/config.py +0 -0
  13. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/exceptions.py +0 -0
  14. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/protocols/__init__.py +0 -0
  15. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/protocols/a2a.py +0 -0
  16. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/protocols/base.py +0 -0
  17. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/simple.py +0 -0
  18. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/testing/__init__.py +0 -0
  19. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/testing/test_helpers.py +0 -0
  20. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/types/__init__.py +0 -0
  21. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/types/core.py +0 -0
  22. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/types/generated.py +0 -0
  23. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/types/tasks.py +0 -0
  24. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/utils/__init__.py +0 -0
  25. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/utils/operation_id.py +0 -0
  26. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/utils/preview_cache.py +0 -0
  27. {adcp-1.4.0 → adcp-1.4.1}/src/adcp/utils/response_parser.py +0 -0
  28. {adcp-1.4.0 → adcp-1.4.1}/src/adcp.egg-info/SOURCES.txt +0 -0
  29. {adcp-1.4.0 → adcp-1.4.1}/src/adcp.egg-info/dependency_links.txt +0 -0
  30. {adcp-1.4.0 → adcp-1.4.1}/src/adcp.egg-info/entry_points.txt +0 -0
  31. {adcp-1.4.0 → adcp-1.4.1}/src/adcp.egg-info/requires.txt +0 -0
  32. {adcp-1.4.0 → adcp-1.4.1}/src/adcp.egg-info/top_level.txt +0 -0
  33. {adcp-1.4.0 → adcp-1.4.1}/tests/test_cli.py +0 -0
  34. {adcp-1.4.0 → adcp-1.4.1}/tests/test_client.py +0 -0
  35. {adcp-1.4.0 → adcp-1.4.1}/tests/test_code_generation.py +0 -0
  36. {adcp-1.4.0 → adcp-1.4.1}/tests/test_discriminated_unions.py +0 -0
  37. {adcp-1.4.0 → adcp-1.4.1}/tests/test_format_id_validation.py +0 -0
  38. {adcp-1.4.0 → adcp-1.4.1}/tests/test_helpers.py +0 -0
  39. {adcp-1.4.0 → adcp-1.4.1}/tests/test_preview_html.py +0 -0
  40. {adcp-1.4.0 → adcp-1.4.1}/tests/test_response_parser.py +0 -0
  41. {adcp-1.4.0 → adcp-1.4.1}/tests/test_simple_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 1.4.0
3
+ Version: 1.4.1
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.4.0"
7
+ version = "1.4.1"
8
8
  description = "Official Python client for the Ad Context Protocol (AdCP)"
9
9
  authors = [
10
10
  {name = "AdCP Community", email = "maintainers@adcontextprotocol.org"}
@@ -150,7 +150,7 @@ from adcp.types.generated import (
150
150
  TaskStatus as GeneratedTaskStatus,
151
151
  )
152
152
 
153
- __version__ = "1.4.0"
153
+ __version__ = "1.4.1"
154
154
 
155
155
  __all__ = [
156
156
  # Client classes
@@ -245,24 +245,12 @@ class MCPAdapter(ProtocolAdapter):
245
245
  # Call the tool using MCP client session
246
246
  result = await session.call_tool(tool_name, params)
247
247
 
248
- # This SDK requires MCP tools to return structuredContent
249
- # The content field may contain human-readable messages but the actual
250
- # response data must be in structuredContent
251
- if not hasattr(result, "structuredContent") or result.structuredContent is None:
252
- raise ValueError(
253
- f"MCP tool {tool_name} did not return structuredContent. "
254
- f"This SDK requires MCP tools to provide structured responses. "
255
- f"Got content: {result.content if hasattr(result, 'content') else 'none'}"
256
- )
248
+ # Check if this is an error response
249
+ is_error = hasattr(result, "isError") and result.isError
257
250
 
258
- # Extract the structured data (required)
259
- data_to_return = result.structuredContent
260
-
261
- # Extract human-readable message from content (optional)
262
- # This is typically a status message like "Found 42 creative formats"
251
+ # Extract human-readable message from content
263
252
  message_text = None
264
253
  if hasattr(result, "content") and result.content:
265
- # Serialize content using the same method used for backward compatibility
266
254
  serialized_content = self._serialize_mcp_content(result.content)
267
255
  if isinstance(serialized_content, list):
268
256
  for item in serialized_content:
@@ -271,6 +259,40 @@ class MCPAdapter(ProtocolAdapter):
271
259
  message_text = item["text"]
272
260
  break
273
261
 
262
+ # Handle error responses
263
+ if is_error:
264
+ # For error responses, structuredContent is optional
265
+ # Use the error message from content as the error
266
+ error_message = message_text or "Tool execution failed"
267
+ if self.agent_config.debug and start_time:
268
+ duration_ms = (time.time() - start_time) * 1000
269
+ debug_info = DebugInfo(
270
+ request=debug_request,
271
+ response={
272
+ "error": error_message,
273
+ "is_error": True,
274
+ },
275
+ duration_ms=duration_ms,
276
+ )
277
+ return TaskResult[Any](
278
+ status=TaskStatus.FAILED,
279
+ error=error_message,
280
+ success=False,
281
+ debug_info=debug_info,
282
+ )
283
+
284
+ # For successful responses, structuredContent is required
285
+ if not hasattr(result, "structuredContent") or result.structuredContent is None:
286
+ raise ValueError(
287
+ f"MCP tool {tool_name} did not return structuredContent. "
288
+ f"This SDK requires MCP tools to provide structured responses "
289
+ f"for successful calls. "
290
+ f"Got content: {result.content if hasattr(result, 'content') else 'none'}"
291
+ )
292
+
293
+ # Extract the structured data (required for success)
294
+ data_to_return = result.structuredContent
295
+
274
296
  if self.agent_config.debug and start_time:
275
297
  duration_ms = (time.time() - start_time) * 1000
276
298
  debug_info = DebugInfo(
@@ -278,7 +300,7 @@ class MCPAdapter(ProtocolAdapter):
278
300
  response={
279
301
  "data": data_to_return,
280
302
  "message": message_text,
281
- "is_error": result.isError if hasattr(result, "isError") else False,
303
+ "is_error": False,
282
304
  },
283
305
  duration_ms=duration_ms,
284
306
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 1.4.0
3
+ Version: 1.4.1
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
@@ -164,6 +164,7 @@ class TestMCPAdapter:
164
164
  # Mock MCP result with structuredContent (required for AdCP)
165
165
  mock_result.content = [{"type": "text", "text": "Success"}]
166
166
  mock_result.structuredContent = {"products": [{"id": "prod1"}]}
167
+ mock_result.isError = False
167
168
  mock_session.call_tool.return_value = mock_result
168
169
 
169
170
  with patch.object(adapter, "_get_session", return_value=mock_session):
@@ -193,6 +194,7 @@ class TestMCPAdapter:
193
194
  # Mock MCP result with structuredContent (preferred over content)
194
195
  mock_result.content = [{"type": "text", "text": "Found 42 creative formats"}]
195
196
  mock_result.structuredContent = {"formats": [{"id": "format1"}, {"id": "format2"}]}
197
+ mock_result.isError = False
196
198
  mock_session.call_tool.return_value = mock_result
197
199
 
198
200
  with patch.object(adapter, "_get_session", return_value=mock_session):
@@ -207,24 +209,46 @@ class TestMCPAdapter:
207
209
 
208
210
  @pytest.mark.asyncio
209
211
  async def test_call_tool_missing_structured_content(self, mcp_config):
210
- """Test tool call fails when structuredContent is missing."""
212
+ """Test tool call fails when structuredContent is missing on successful response."""
211
213
  adapter = MCPAdapter(mcp_config)
212
214
 
213
215
  mock_session = AsyncMock()
214
216
  mock_result = MagicMock()
215
- # Mock MCP result WITHOUT structuredContent (invalid for AdCP)
217
+ # Mock MCP result WITHOUT structuredContent and isError=False (invalid)
216
218
  mock_result.content = [{"type": "text", "text": "Success"}]
217
219
  mock_result.structuredContent = None
220
+ mock_result.isError = False
218
221
  mock_session.call_tool.return_value = mock_result
219
222
 
220
223
  with patch.object(adapter, "_get_session", return_value=mock_session):
221
224
  result = await adapter._call_mcp_tool("get_products", {"brief": "test"})
222
225
 
223
- # Verify error handling for missing structuredContent
226
+ # Verify error handling for missing structuredContent on success
224
227
  assert result.success is False
225
228
  assert result.status == TaskStatus.FAILED
226
229
  assert "did not return structuredContent" in result.error
227
230
 
231
+ @pytest.mark.asyncio
232
+ async def test_call_tool_error_without_structured_content(self, mcp_config):
233
+ """Test tool call handles error responses without structuredContent gracefully."""
234
+ adapter = MCPAdapter(mcp_config)
235
+
236
+ mock_session = AsyncMock()
237
+ mock_result = MagicMock()
238
+ # Mock MCP error response WITHOUT structuredContent (valid for errors)
239
+ mock_result.content = [{"type": "text", "text": "brand_manifest must provide brand information"}]
240
+ mock_result.structuredContent = None
241
+ mock_result.isError = True
242
+ mock_session.call_tool.return_value = mock_result
243
+
244
+ with patch.object(adapter, "_get_session", return_value=mock_session):
245
+ result = await adapter._call_mcp_tool("get_products", {"brief": "test"})
246
+
247
+ # Verify error is handled gracefully
248
+ assert result.success is False
249
+ assert result.status == TaskStatus.FAILED
250
+ assert result.error == "brand_manifest must provide brand information"
251
+
228
252
  @pytest.mark.asyncio
229
253
  async def test_call_tool_error(self, mcp_config):
230
254
  """Test tool call error via MCP."""
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
File without changes
File without changes
File without changes
File without changes
File without changes