adcp 1.0.2__py3-none-any.whl → 1.0.4__py3-none-any.whl

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.
@@ -0,0 +1,122 @@
1
+ from __future__ import annotations
2
+
3
+ """Utilities for parsing protocol responses into structured types."""
4
+
5
+ import json
6
+ import logging
7
+ from typing import Any, TypeVar
8
+
9
+ from pydantic import BaseModel, ValidationError
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ T = TypeVar("T", bound=BaseModel)
14
+
15
+
16
+ def parse_mcp_content(content: list[dict[str, Any]], response_type: type[T]) -> T:
17
+ """
18
+ Parse MCP content array into structured response type.
19
+
20
+ MCP tools return content as a list of content items:
21
+ [{"type": "text", "text": "..."}, {"type": "resource", ...}]
22
+
23
+ The MCP adapter is responsible for serializing MCP SDK Pydantic objects
24
+ to plain dicts before calling this function.
25
+
26
+ For AdCP, we expect JSON data in text content items.
27
+
28
+ Args:
29
+ content: MCP content array (list of plain dicts)
30
+ response_type: Expected Pydantic model type
31
+
32
+ Returns:
33
+ Parsed and validated response object
34
+
35
+ Raises:
36
+ ValueError: If content cannot be parsed into expected type
37
+ """
38
+ if not content:
39
+ raise ValueError("Empty MCP content array")
40
+
41
+ # Look for text content items that might contain JSON
42
+ for item in content:
43
+ if item.get("type") == "text":
44
+ text = item.get("text", "")
45
+ if not text:
46
+ continue
47
+
48
+ try:
49
+ # Try parsing as JSON
50
+ data = json.loads(text)
51
+ # Validate against expected schema
52
+ return response_type.model_validate(data)
53
+ except json.JSONDecodeError:
54
+ # Not JSON, try next item
55
+ continue
56
+ except ValidationError as e:
57
+ logger.warning(
58
+ f"MCP content doesn't match expected schema {response_type.__name__}: {e}"
59
+ )
60
+ raise ValueError(f"MCP response doesn't match expected schema: {e}") from e
61
+ elif item.get("type") == "resource":
62
+ # Resource content might have structured data
63
+ try:
64
+ return response_type.model_validate(item)
65
+ except ValidationError:
66
+ # Try next item
67
+ continue
68
+
69
+ # If we get here, no content item could be parsed
70
+ # Include content preview for debugging (first 2 items, max 500 chars each)
71
+ content_preview = json.dumps(content[:2], indent=2, default=str)
72
+ if len(content_preview) > 500:
73
+ content_preview = content_preview[:500] + "..."
74
+
75
+ raise ValueError(
76
+ f"No valid {response_type.__name__} data found in MCP content. "
77
+ f"Content types: {[item.get('type') for item in content]}. "
78
+ f"Content preview:\n{content_preview}"
79
+ )
80
+
81
+
82
+ def parse_json_or_text(data: Any, response_type: type[T]) -> T:
83
+ """
84
+ Parse data that might be JSON string, dict, or other format.
85
+
86
+ Used by A2A adapter for flexible response parsing.
87
+
88
+ Args:
89
+ data: Response data (string, dict, or other)
90
+ response_type: Expected Pydantic model type
91
+
92
+ Returns:
93
+ Parsed and validated response object
94
+
95
+ Raises:
96
+ ValueError: If data cannot be parsed into expected type
97
+ """
98
+ # If already a dict, try direct validation
99
+ if isinstance(data, dict):
100
+ try:
101
+ return response_type.model_validate(data)
102
+ except ValidationError as e:
103
+ raise ValueError(
104
+ f"Response doesn't match expected schema {response_type.__name__}: {e}"
105
+ ) from e
106
+
107
+ # If string, try JSON parsing
108
+ if isinstance(data, str):
109
+ try:
110
+ parsed = json.loads(data)
111
+ return response_type.model_validate(parsed)
112
+ except json.JSONDecodeError as e:
113
+ raise ValueError(f"Response is not valid JSON: {e}") from e
114
+ except ValidationError as e:
115
+ raise ValueError(
116
+ f"Response doesn't match expected schema {response_type.__name__}: {e}"
117
+ ) from e
118
+
119
+ # Unsupported type
120
+ raise ValueError(
121
+ f"Cannot parse response of type {type(data).__name__} into {response_type.__name__}"
122
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adcp
3
- Version: 1.0.2
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
@@ -0,0 +1,22 @@
1
+ adcp/__init__.py,sha256=3ARviLlJxnjYaDMIRFLceNsaES6z9ft81qKRYeq6Eq4,2512
2
+ adcp/__main__.py,sha256=4rjwH3i52Dsy-Pb0hw5hapQuEG8-bW2tTKKB0HsVsSs,12716
3
+ adcp/client.py,sha256=iIZUy5j25H48gGYlR_VuVsB9zP6SF1HtRssdPs2VJkc,24232
4
+ adcp/config.py,sha256=Vsy7ZPOI8G3fB_i5Nk-CHbC7wdasCUWuKlos0fwA0kY,2017
5
+ adcp/exceptions.py,sha256=dNRMKV23DlkGKyB9Xmt6MtlhvDu1crjzD_en4nAEwDY,4399
6
+ adcp/protocols/__init__.py,sha256=6UFwACQ0QadBUzy17wUROHqsJDp8ztPW2jzyl53Zh_g,262
7
+ adcp/protocols/a2a.py,sha256=TN26ac98h2NUZTTs39Tyd6pVoS3k-sASuLKhLpdYV-A,12255
8
+ adcp/protocols/base.py,sha256=Tdxg1hefWVDvRP7q3yYbtPlIfUr01luPG0oT773qi30,4906
9
+ adcp/protocols/mcp.py,sha256=JNJdouaDQ66H9pUtsaZtV8FWoLGf28QM29tOLESDxsk,14664
10
+ adcp/types/__init__.py,sha256=3E_TJUXqQQFcjmSZZSPLwqBP3s_ijsH2LDeuOU-MP30,402
11
+ adcp/types/core.py,sha256=w6CLD2K0riAHUnrktYmQV7qnkO-6Ab4-CN67YSagAE4,4731
12
+ adcp/types/generated.py,sha256=KoILEa5Gg0tsjMYoqDEWulKvPzS0333L3qj75AHr4yI,50855
13
+ adcp/types/tasks.py,sha256=Ae9TSwG2F7oWXTcl4TvLhAzinbQkHNGF1Pc0q8RMNNM,23424
14
+ adcp/utils/__init__.py,sha256=uetvSJB19CjQbtwEYZiTnumJG11GsafQmXm5eR3hL7E,153
15
+ adcp/utils/operation_id.py,sha256=wQX9Bb5epXzRq23xoeYPTqzu5yLuhshg7lKJZihcM2k,294
16
+ adcp/utils/response_parser.py,sha256=NQTLlbvmnM_tE4B5w3oB1Wshny1p-Uh8IWbghlwoNJc,4057
17
+ adcp-1.0.4.dist-info/licenses/LICENSE,sha256=PF39NR3Ae8PLgBhg3Uxw6ju7iGVIf8hfv9LRWQdii_U,629
18
+ adcp-1.0.4.dist-info/METADATA,sha256=ahZm1ty9AJ28PK5cC5_lrjkXPacgO3-3VMYn5ncfzJ4,12724
19
+ adcp-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ adcp-1.0.4.dist-info/entry_points.txt,sha256=DQKpcGsJX8DtVI_SGApQ7tNvqUB4zkTLaTAEpFgmi3U,44
21
+ adcp-1.0.4.dist-info/top_level.txt,sha256=T1_NF0GefncFU9v_k56oDwKSJREyCqIM8lAwNZf0EOs,5
22
+ adcp-1.0.4.dist-info/RECORD,,
@@ -1,21 +0,0 @@
1
- adcp/__init__.py,sha256=P-Lws8JQAoeN82lIPhabzOPf5CSgTRDWNSQswb9aBK4,2512
2
- adcp/__main__.py,sha256=MR7hiJu0cM7wkBAMpJ9wq9IXWFjWeD5gEOvJOJawnW8,8900
3
- adcp/client.py,sha256=E7SEqZarXuT76nURHWG_O9thNauNW1yEesFlF2lVXjg,20900
4
- adcp/config.py,sha256=Vsy7ZPOI8G3fB_i5Nk-CHbC7wdasCUWuKlos0fwA0kY,2017
5
- adcp/exceptions.py,sha256=dNRMKV23DlkGKyB9Xmt6MtlhvDu1crjzD_en4nAEwDY,4399
6
- adcp/protocols/__init__.py,sha256=6UFwACQ0QadBUzy17wUROHqsJDp8ztPW2jzyl53Zh_g,262
7
- adcp/protocols/a2a.py,sha256=c5PZ1SlZe2TPzx-eFu-RH9P-h98CW4F5PBdgsU5k8HE,10281
8
- adcp/protocols/base.py,sha256=fG4tk4UMVSkBE3zv5b7bPEOSEXOCTI2oFyy9V6H406Y,1183
9
- adcp/protocols/mcp.py,sha256=Oo0i7jyTnmQPoLeKuP6YBCmCzi1WazUDf3SzxMAU6lA,11163
10
- adcp/types/__init__.py,sha256=3E_TJUXqQQFcjmSZZSPLwqBP3s_ijsH2LDeuOU-MP30,402
11
- adcp/types/core.py,sha256=w6CLD2K0riAHUnrktYmQV7qnkO-6Ab4-CN67YSagAE4,4731
12
- adcp/types/generated.py,sha256=5vbr113-rhNJSMePAEvQSPtqqEqiloVw1F8BeT4tk3o,49973
13
- adcp/types/tasks.py,sha256=iwNoV-1_BxH3PZxmfg2rgZHDUN-o_Ob7zgPNbKtyNPM,21553
14
- adcp/utils/__init__.py,sha256=uetvSJB19CjQbtwEYZiTnumJG11GsafQmXm5eR3hL7E,153
15
- adcp/utils/operation_id.py,sha256=wQX9Bb5epXzRq23xoeYPTqzu5yLuhshg7lKJZihcM2k,294
16
- adcp-1.0.2.dist-info/licenses/LICENSE,sha256=PF39NR3Ae8PLgBhg3Uxw6ju7iGVIf8hfv9LRWQdii_U,629
17
- adcp-1.0.2.dist-info/METADATA,sha256=q2TkYphUBt9eTOWTZ2e774Xj1cuzxjeOgEvvTAPtOZ8,12724
18
- adcp-1.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
- adcp-1.0.2.dist-info/entry_points.txt,sha256=DQKpcGsJX8DtVI_SGApQ7tNvqUB4zkTLaTAEpFgmi3U,44
20
- adcp-1.0.2.dist-info/top_level.txt,sha256=T1_NF0GefncFU9v_k56oDwKSJREyCqIM8lAwNZf0EOs,5
21
- adcp-1.0.2.dist-info/RECORD,,
File without changes