agentic-data-contracts 0.2.2__tar.gz → 0.2.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 (77) hide show
  1. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/CHANGELOG.md +19 -0
  2. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/PKG-INFO +11 -12
  3. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/README.md +8 -9
  4. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/pyproject.toml +2 -2
  5. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/__init__.py +7 -1
  6. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/adapters/base.py +1 -0
  7. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/adapters/duckdb.py +12 -0
  8. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/core/contract.py +18 -0
  9. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/tools/factory.py +9 -0
  10. agentic_data_contracts-0.2.4/src/agentic_data_contracts/tools/sdk.py +71 -0
  11. agentic_data_contracts-0.2.4/tests/test_core/test_wildcard_tables.py +106 -0
  12. agentic_data_contracts-0.2.4/tests/test_tools/test_sdk.py +116 -0
  13. agentic_data_contracts-0.2.4/tests/test_tools/test_wildcard_tools.py +76 -0
  14. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/uv.lock +9 -9
  15. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/.github/dependabot.yml +0 -0
  16. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/.github/workflows/ci.yml +0 -0
  17. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/.gitignore +0 -0
  18. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/.pre-commit-config.yaml +0 -0
  19. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/.python-version +0 -0
  20. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/CLAUDE.md +0 -0
  21. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/LICENSE +0 -0
  22. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/docs/architecture.md +0 -0
  23. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/examples/revenue_agent/agent.py +0 -0
  24. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/examples/revenue_agent/contract.yml +0 -0
  25. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/examples/revenue_agent/semantic.yml +0 -0
  26. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/examples/revenue_agent/setup_db.py +0 -0
  27. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/adapters/__init__.py +0 -0
  28. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/bridge/__init__.py +0 -0
  29. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/bridge/compiler.py +0 -0
  30. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/core/__init__.py +0 -0
  31. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/core/schema.py +0 -0
  32. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/core/session.py +0 -0
  33. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/py.typed +0 -0
  34. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/semantic/__init__.py +0 -0
  35. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/semantic/base.py +0 -0
  36. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/semantic/cube.py +0 -0
  37. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/semantic/dbt.py +0 -0
  38. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/semantic/yaml_source.py +0 -0
  39. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/tools/__init__.py +0 -0
  40. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/tools/middleware.py +0 -0
  41. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/validation/__init__.py +0 -0
  42. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/validation/checkers.py +0 -0
  43. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/validation/explain.py +0 -0
  44. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/src/agentic_data_contracts/validation/validator.py +0 -0
  45. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/__init__.py +0 -0
  46. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/conftest.py +0 -0
  47. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/fixtures/minimal_contract.yml +0 -0
  48. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/fixtures/sample_cube_schema.yml +0 -0
  49. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/fixtures/sample_dbt_manifest.json +0 -0
  50. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/fixtures/semantic_source.yml +0 -0
  51. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/fixtures/valid_contract.yml +0 -0
  52. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_adapters/__init__.py +0 -0
  53. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_adapters/test_duckdb.py +0 -0
  54. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_bridge/__init__.py +0 -0
  55. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_bridge/test_compiler.py +0 -0
  56. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_core/__init__.py +0 -0
  57. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_core/test_contract.py +0 -0
  58. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_core/test_load_semantic_source.py +0 -0
  59. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_core/test_schema.py +0 -0
  60. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_core/test_sdk_config.py +0 -0
  61. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_core/test_session.py +0 -0
  62. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_core/test_system_prompt_metrics.py +0 -0
  63. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_public_api.py +0 -0
  64. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_semantic/__init__.py +0 -0
  65. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_semantic/test_cube.py +0 -0
  66. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_semantic/test_dbt.py +0 -0
  67. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_semantic/test_search.py +0 -0
  68. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_semantic/test_yaml_source.py +0 -0
  69. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_tools/__init__.py +0 -0
  70. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_tools/test_auto_load.py +0 -0
  71. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_tools/test_factory.py +0 -0
  72. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_tools/test_middleware.py +0 -0
  73. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_tools/test_semantic_tools.py +0 -0
  74. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_validation/__init__.py +0 -0
  75. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_validation/test_checkers.py +0 -0
  76. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_validation/test_explain.py +0 -0
  77. {agentic_data_contracts-0.2.2 → agentic_data_contracts-0.2.4}/tests/test_validation/test_validator.py +0 -0
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.2.4] - 2026-03-29
6
+
7
+ ### Added
8
+
9
+ - **Wildcard table support**: Use `tables: ["*"]` in `allowed_tables` to allow all tables in a schema, discovered from the database at runtime via `adapter.list_tables()`
10
+ - **`DataContract.resolve_tables(adapter)`**: Expands wildcard entries using the database adapter; called automatically by `create_tools()` when an adapter is provided
11
+ - **`DatabaseAdapter.list_tables(schema)`**: New protocol method for listing tables in a schema; implemented in `DuckDBAdapter` via `information_schema.tables`
12
+
13
+ ## [0.2.3] - 2026-03-29
14
+
15
+ ### Added
16
+
17
+ - **SDK MCP server convenience method**: `create_sdk_mcp_server(contract, adapter=...)` wraps all 10 tools with the SDK's `@tool` decorator and bundles them into a ready-to-use MCP server for `ClaudeAgentOptions.mcp_servers`
18
+ - **Top-level export**: `from agentic_data_contracts import create_sdk_mcp_server`
19
+
20
+ ### Changed
21
+
22
+ - **SDK dependency**: Bumped `claude-agent-sdk` minimum to `>=0.1.52`
23
+
5
24
  ## [0.2.2] - 2026-03-28
6
25
 
7
26
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-data-contracts
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: YAML-first data contract governance for AI agents
5
5
  Project-URL: Homepage, https://github.com/flyersworder/agentic-data-contracts
6
6
  Project-URL: Repository, https://github.com/flyersworder/agentic-data-contracts
@@ -27,10 +27,10 @@ Requires-Dist: thefuzz>=0.22.1
27
27
  Provides-Extra: agent-contracts
28
28
  Requires-Dist: ai-agent-contracts>=0.2.0; extra == 'agent-contracts'
29
29
  Provides-Extra: agent-sdk
30
- Requires-Dist: claude-agent-sdk; extra == 'agent-sdk'
30
+ Requires-Dist: claude-agent-sdk>=0.1.52; extra == 'agent-sdk'
31
31
  Provides-Extra: all
32
32
  Requires-Dist: ai-agent-contracts>=0.2.0; extra == 'all'
33
- Requires-Dist: claude-agent-sdk; extra == 'all'
33
+ Requires-Dist: claude-agent-sdk>=0.1.52; extra == 'all'
34
34
  Requires-Dist: duckdb; extra == 'all'
35
35
  Requires-Dist: google-cloud-bigquery; extra == 'all'
36
36
  Requires-Dist: psycopg2-binary; extra == 'all'
@@ -117,7 +117,9 @@ semantic:
117
117
  path: "./semantic.yml"
118
118
  allowed_tables:
119
119
  - schema: analytics
120
- tables: [orders, customers, subscriptions]
120
+ tables: ["*"] # all tables in schema (discovered from database)
121
+ - schema: marketing
122
+ tables: [campaigns] # or list specific tables
121
123
  forbidden_operations: [DELETE, DROP, TRUNCATE, UPDATE, INSERT]
122
124
  rules:
123
125
  - name: tenant_isolation
@@ -150,29 +152,26 @@ adapter = DuckDBAdapter("analytics.duckdb")
150
152
  tools = create_tools(dc, adapter=adapter)
151
153
  ```
152
154
 
153
- ### 3. Use with the Claude Agent SDK
155
+ ### 3. Use with the Claude Agent SDK (requires `claude-agent-sdk>=0.1.52`)
154
156
 
155
157
  ```python
156
158
  import asyncio
159
+ from agentic_data_contracts import create_sdk_mcp_server
157
160
  from claude_agent_sdk import (
158
161
  ClaudeAgentOptions,
159
162
  AssistantMessage,
160
163
  TextBlock,
161
- create_sdk_mcp_server,
162
164
  query,
163
165
  )
164
166
 
165
- server = create_sdk_mcp_server(name="data-contracts", version="1.0.0", tools=tools)
166
-
167
- # Contract limits map to SDK options (token_budget → task_budget, max_retries → max_turns)
168
- sdk_config = dc.to_sdk_config()
167
+ # One-liner: wraps all 10 tools and bundles into an SDK MCP server
168
+ server = create_sdk_mcp_server(dc, adapter=adapter)
169
169
 
170
170
  options = ClaudeAgentOptions(
171
171
  model="claude-sonnet-4-6",
172
172
  system_prompt=f"You are a revenue analytics assistant.\n\n{dc.to_system_prompt()}",
173
173
  mcp_servers={"dc": server},
174
- allowed_tools=[f"mcp__dc__{t.name}" for t in tools],
175
- **sdk_config,
174
+ **dc.to_sdk_config(), # token_budget task_budget, max_retries → max_turns
176
175
  )
177
176
 
178
177
  async def run(prompt: str) -> None:
@@ -64,7 +64,9 @@ semantic:
64
64
  path: "./semantic.yml"
65
65
  allowed_tables:
66
66
  - schema: analytics
67
- tables: [orders, customers, subscriptions]
67
+ tables: ["*"] # all tables in schema (discovered from database)
68
+ - schema: marketing
69
+ tables: [campaigns] # or list specific tables
68
70
  forbidden_operations: [DELETE, DROP, TRUNCATE, UPDATE, INSERT]
69
71
  rules:
70
72
  - name: tenant_isolation
@@ -97,29 +99,26 @@ adapter = DuckDBAdapter("analytics.duckdb")
97
99
  tools = create_tools(dc, adapter=adapter)
98
100
  ```
99
101
 
100
- ### 3. Use with the Claude Agent SDK
102
+ ### 3. Use with the Claude Agent SDK (requires `claude-agent-sdk>=0.1.52`)
101
103
 
102
104
  ```python
103
105
  import asyncio
106
+ from agentic_data_contracts import create_sdk_mcp_server
104
107
  from claude_agent_sdk import (
105
108
  ClaudeAgentOptions,
106
109
  AssistantMessage,
107
110
  TextBlock,
108
- create_sdk_mcp_server,
109
111
  query,
110
112
  )
111
113
 
112
- server = create_sdk_mcp_server(name="data-contracts", version="1.0.0", tools=tools)
113
-
114
- # Contract limits map to SDK options (token_budget → task_budget, max_retries → max_turns)
115
- sdk_config = dc.to_sdk_config()
114
+ # One-liner: wraps all 10 tools and bundles into an SDK MCP server
115
+ server = create_sdk_mcp_server(dc, adapter=adapter)
116
116
 
117
117
  options = ClaudeAgentOptions(
118
118
  model="claude-sonnet-4-6",
119
119
  system_prompt=f"You are a revenue analytics assistant.\n\n{dc.to_system_prompt()}",
120
120
  mcp_servers={"dc": server},
121
- allowed_tools=[f"mcp__dc__{t.name}" for t in tools],
122
- **sdk_config,
121
+ **dc.to_sdk_config(), # token_budget task_budget, max_retries → max_turns
123
122
  )
124
123
 
125
124
  async def run(prompt: str) -> None:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentic-data-contracts"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  description = "YAML-first data contract governance for AI agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -41,7 +41,7 @@ Issues = "https://github.com/flyersworder/agentic-data-contracts/issues"
41
41
  Documentation = "https://github.com/flyersworder/agentic-data-contracts/blob/main/docs/architecture.md"
42
42
 
43
43
  [project.optional-dependencies]
44
- agent-sdk = ["claude-agent-sdk"]
44
+ agent-sdk = ["claude-agent-sdk>=0.1.52"]
45
45
  agent-contracts = ["ai-agent-contracts>=0.2.0"]
46
46
  bigquery = ["google-cloud-bigquery"]
47
47
  snowflake = ["snowflake-connector-python"]
@@ -3,5 +3,11 @@
3
3
  from agentic_data_contracts.core.contract import DataContract
4
4
  from agentic_data_contracts.tools.factory import create_tools
5
5
  from agentic_data_contracts.tools.middleware import contract_middleware
6
+ from agentic_data_contracts.tools.sdk import create_sdk_mcp_server
6
7
 
7
- __all__ = ["DataContract", "create_tools", "contract_middleware"]
8
+ __all__ = [
9
+ "DataContract",
10
+ "create_tools",
11
+ "contract_middleware",
12
+ "create_sdk_mcp_server",
13
+ ]
@@ -37,6 +37,7 @@ class DatabaseAdapter(Protocol):
37
37
  def execute(self, sql: str) -> QueryResult: ...
38
38
  def explain(self, sql: str) -> ExplainResult: ...
39
39
  def describe_table(self, schema: str, table: str) -> TableSchema: ...
40
+ def list_tables(self, schema: str) -> list[str]: ...
40
41
 
41
42
  @property
42
43
  def dialect(self) -> str: ...
@@ -59,6 +59,18 @@ class DuckDBAdapter:
59
59
  last_estimate = int(match.group(1))
60
60
  return last_estimate
61
61
 
62
+ def list_tables(self, schema: str) -> list[str]:
63
+ rows = self.connection.execute(
64
+ """
65
+ SELECT table_name
66
+ FROM information_schema.tables
67
+ WHERE table_schema = ?
68
+ ORDER BY table_name
69
+ """,
70
+ [schema],
71
+ ).fetchall()
72
+ return [row[0] for row in rows]
73
+
62
74
  def describe_table(self, schema: str, table: str) -> TableSchema:
63
75
  rows = self.connection.execute(
64
76
  """
@@ -14,6 +14,7 @@ from agentic_data_contracts.core.schema import (
14
14
  )
15
15
 
16
16
  if TYPE_CHECKING:
17
+ from agentic_data_contracts.adapters.base import DatabaseAdapter
17
18
  from agentic_data_contracts.semantic.base import SemanticSource
18
19
 
19
20
 
@@ -38,10 +39,27 @@ class DataContract:
38
39
  schema = DataContractSchema.model_validate(raw)
39
40
  return cls(schema=schema)
40
41
 
42
+ def has_wildcard_tables(self) -> bool:
43
+ """Check if any schema uses wildcard ('*') for tables."""
44
+ return any("*" in entry.tables for entry in self.schema.semantic.allowed_tables)
45
+
46
+ def resolve_tables(self, adapter: DatabaseAdapter) -> None:
47
+ """Expand wildcard tables using the database adapter.
48
+
49
+ Replaces ["*"] entries with actual table names from the database.
50
+ Call this once after creating the adapter. Results are cached
51
+ on the schema object.
52
+ """
53
+ for entry in self.schema.semantic.allowed_tables:
54
+ if "*" in entry.tables:
55
+ entry.tables = adapter.list_tables(entry.schema_)
56
+
41
57
  def allowed_table_names(self) -> list[str]:
42
58
  names: list[str] = []
43
59
  for entry in self.schema.semantic.allowed_tables:
44
60
  for table in entry.tables:
61
+ if table == "*":
62
+ continue # unresolved wildcard — skip
45
63
  names.append(f"{entry.schema_}.{table}")
46
64
  return names
47
65
 
@@ -41,6 +41,10 @@ def create_tools(
41
41
  if semantic_source is None:
42
42
  semantic_source = contract.load_semantic_source()
43
43
 
44
+ # Resolve wildcard tables if adapter is available
45
+ if adapter is not None and contract.has_wildcard_tables():
46
+ contract.resolve_tables(adapter)
47
+
44
48
  dialect = adapter.dialect if adapter else None
45
49
  validator = Validator(contract, dialect=dialect, explain_adapter=adapter)
46
50
 
@@ -60,6 +64,11 @@ def create_tools(
60
64
  for entry in contract.schema.semantic.allowed_tables:
61
65
  if schema_filter and entry.schema_ != schema_filter:
62
66
  continue
67
+ if "*" in entry.tables:
68
+ return _text_response(
69
+ f"Schema '{entry.schema_}' uses wildcard tables"
70
+ " but no database adapter is available to resolve them."
71
+ )
63
72
  for table in entry.tables:
64
73
  info: dict[str, Any] = {
65
74
  "schema": entry.schema_,
@@ -0,0 +1,71 @@
1
+ """Claude Agent SDK integration — wraps ToolDefs into an SDK MCP server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from agentic_data_contracts.adapters.base import DatabaseAdapter
8
+ from agentic_data_contracts.core.contract import DataContract
9
+ from agentic_data_contracts.core.session import ContractSession
10
+ from agentic_data_contracts.semantic.base import SemanticSource
11
+ from agentic_data_contracts.tools.factory import ToolDef, create_tools
12
+
13
+
14
+ def create_sdk_mcp_server(
15
+ contract: DataContract,
16
+ *,
17
+ adapter: DatabaseAdapter | None = None,
18
+ semantic_source: SemanticSource | None = None,
19
+ session: ContractSession | None = None,
20
+ tools: list[ToolDef] | None = None,
21
+ server_name: str = "data-contracts",
22
+ server_version: str = "1.0.0",
23
+ ) -> Any:
24
+ """Create a Claude Agent SDK MCP server from a DataContract.
25
+
26
+ Wraps all 10 contract tools with the SDK's @tool decorator and
27
+ bundles them into an MCP server ready for ClaudeAgentOptions.mcp_servers.
28
+
29
+ Args:
30
+ contract: The data contract to enforce.
31
+ adapter: Optional database adapter for query execution.
32
+ semantic_source: Optional semantic source (auto-loaded if not given).
33
+ session: Optional session for tracking enforcement state.
34
+ tools: Pre-built ToolDefs (if None, created via create_tools).
35
+ server_name: Name for the MCP server.
36
+ server_version: Version for the MCP server.
37
+
38
+ Returns:
39
+ McpSdkServerConfig ready for ClaudeAgentOptions.mcp_servers.
40
+
41
+ Raises:
42
+ ImportError: If claude-agent-sdk is not installed.
43
+ """
44
+ try:
45
+ from claude_agent_sdk import create_sdk_mcp_server as _create_server
46
+ from claude_agent_sdk import tool as sdk_tool
47
+ except ImportError:
48
+ msg = (
49
+ "claude-agent-sdk is required for SDK integration. "
50
+ "Install with: pip install agentic-data-contracts[agent-sdk]"
51
+ )
52
+ raise ImportError(msg) from None
53
+
54
+ if tools is None:
55
+ tools = create_tools(
56
+ contract,
57
+ adapter=adapter,
58
+ semantic_source=semantic_source,
59
+ session=session,
60
+ )
61
+
62
+ sdk_tools = []
63
+ for t in tools:
64
+ decorated = sdk_tool(t.name, t.description, t.input_schema)(t.callable)
65
+ sdk_tools.append(decorated)
66
+
67
+ return _create_server(
68
+ name=server_name,
69
+ version=server_version,
70
+ tools=sdk_tools,
71
+ )
@@ -0,0 +1,106 @@
1
+ """Tests for wildcard table support in allowed_tables."""
2
+
3
+ from agentic_data_contracts.adapters.duckdb import DuckDBAdapter
4
+ from agentic_data_contracts.core.contract import DataContract
5
+ from agentic_data_contracts.core.schema import (
6
+ AllowedTable,
7
+ DataContractSchema,
8
+ SemanticConfig,
9
+ )
10
+
11
+
12
+ def _make_contract(tables_config: list[dict]) -> DataContract:
13
+ allowed = [AllowedTable.model_validate(t) for t in tables_config]
14
+ schema = DataContractSchema(
15
+ name="test",
16
+ semantic=SemanticConfig(allowed_tables=allowed),
17
+ )
18
+ return DataContract(schema)
19
+
20
+
21
+ def _make_adapter() -> DuckDBAdapter:
22
+ db = DuckDBAdapter(":memory:")
23
+ db.connection.execute("""
24
+ CREATE SCHEMA IF NOT EXISTS analytics;
25
+ CREATE TABLE analytics.orders (id INTEGER);
26
+ CREATE TABLE analytics.customers (id INTEGER);
27
+ CREATE TABLE analytics.products (id INTEGER);
28
+ CREATE SCHEMA IF NOT EXISTS raw;
29
+ CREATE TABLE raw.events (id INTEGER);
30
+ """)
31
+ return db
32
+
33
+
34
+ def test_has_wildcard_tables_true() -> None:
35
+ dc = _make_contract([{"schema": "analytics", "tables": ["*"]}])
36
+ assert dc.has_wildcard_tables()
37
+
38
+
39
+ def test_has_wildcard_tables_false() -> None:
40
+ dc = _make_contract(
41
+ [
42
+ {"schema": "analytics", "tables": ["orders"]},
43
+ ]
44
+ )
45
+ assert not dc.has_wildcard_tables()
46
+
47
+
48
+ def test_resolve_tables_expands_wildcard() -> None:
49
+ dc = _make_contract([{"schema": "analytics", "tables": ["*"]}])
50
+ adapter = _make_adapter()
51
+ dc.resolve_tables(adapter)
52
+
53
+ names = dc.allowed_table_names()
54
+ assert "analytics.orders" in names
55
+ assert "analytics.customers" in names
56
+ assert "analytics.products" in names
57
+ assert not any(n.startswith("raw.") for n in names)
58
+
59
+
60
+ def test_resolve_tables_mixed() -> None:
61
+ dc = _make_contract(
62
+ [
63
+ {"schema": "analytics", "tables": ["*"]},
64
+ {"schema": "raw", "tables": []},
65
+ ]
66
+ )
67
+ adapter = _make_adapter()
68
+ dc.resolve_tables(adapter)
69
+
70
+ names = dc.allowed_table_names()
71
+ assert "analytics.orders" in names
72
+ assert not any(n.startswith("raw.") for n in names)
73
+
74
+
75
+ def test_resolve_tables_preserves_explicit() -> None:
76
+ dc = _make_contract(
77
+ [
78
+ {"schema": "analytics", "tables": ["orders"]},
79
+ ]
80
+ )
81
+ adapter = _make_adapter()
82
+ dc.resolve_tables(adapter)
83
+
84
+ names = dc.allowed_table_names()
85
+ assert names == ["analytics.orders"]
86
+
87
+
88
+ def test_unresolved_wildcard_skipped() -> None:
89
+ dc = _make_contract([{"schema": "analytics", "tables": ["*"]}])
90
+ # Without calling resolve_tables, wildcard is skipped
91
+ names = dc.allowed_table_names()
92
+ assert names == []
93
+
94
+
95
+ def test_adapter_list_tables() -> None:
96
+ adapter = _make_adapter()
97
+ tables = adapter.list_tables("analytics")
98
+ assert "orders" in tables
99
+ assert "customers" in tables
100
+ assert "products" in tables
101
+
102
+
103
+ def test_adapter_list_tables_empty_schema() -> None:
104
+ adapter = _make_adapter()
105
+ tables = adapter.list_tables("nonexistent")
106
+ assert tables == []
@@ -0,0 +1,116 @@
1
+ """Tests for Claude Agent SDK MCP server integration."""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from agentic_data_contracts.adapters.duckdb import DuckDBAdapter
8
+ from agentic_data_contracts.core.contract import DataContract
9
+ from agentic_data_contracts.core.schema import (
10
+ AllowedTable,
11
+ DataContractSchema,
12
+ SemanticConfig,
13
+ )
14
+ from agentic_data_contracts.tools.factory import create_tools
15
+ from agentic_data_contracts.tools.sdk import create_sdk_mcp_server
16
+
17
+
18
+ @pytest.fixture
19
+ def contract_no_source() -> DataContract:
20
+ """Contract without a semantic source — avoids file path issues."""
21
+ schema = DataContractSchema(
22
+ name="test",
23
+ semantic=SemanticConfig(
24
+ allowed_tables=[
25
+ AllowedTable.model_validate(
26
+ {"schema": "analytics", "tables": ["orders"]}
27
+ ),
28
+ ],
29
+ ),
30
+ )
31
+ return DataContract(schema)
32
+
33
+
34
+ @pytest.fixture
35
+ def contract_with_source(fixtures_dir: Path) -> DataContract:
36
+ """Contract with semantic source pointing to real fixture."""
37
+ from agentic_data_contracts.core.schema import (
38
+ SemanticSource as SemanticSourceConfig,
39
+ )
40
+
41
+ schema = DataContractSchema(
42
+ name="test",
43
+ semantic=SemanticConfig(
44
+ source=SemanticSourceConfig(
45
+ type="yaml",
46
+ path=str(fixtures_dir / "semantic_source.yml"),
47
+ ),
48
+ allowed_tables=[
49
+ AllowedTable.model_validate(
50
+ {"schema": "analytics", "tables": ["orders"]}
51
+ ),
52
+ ],
53
+ ),
54
+ )
55
+ return DataContract(schema)
56
+
57
+
58
+ @pytest.fixture
59
+ def adapter() -> DuckDBAdapter:
60
+ db = DuckDBAdapter(":memory:")
61
+ db.connection.execute(
62
+ """
63
+ CREATE SCHEMA IF NOT EXISTS analytics;
64
+ CREATE TABLE analytics.orders (
65
+ id INTEGER, amount DECIMAL(10,2), tenant_id VARCHAR
66
+ );
67
+ INSERT INTO analytics.orders VALUES (1, 100.00, 'acme');
68
+ """
69
+ )
70
+ return db
71
+
72
+
73
+ def test_create_sdk_mcp_server_returns_config(
74
+ contract_no_source: DataContract, adapter: DuckDBAdapter
75
+ ) -> None:
76
+ server = create_sdk_mcp_server(contract_no_source, adapter=adapter)
77
+ assert server is not None
78
+
79
+
80
+ def test_create_sdk_mcp_server_with_prebuilt_tools(
81
+ contract_no_source: DataContract, adapter: DuckDBAdapter
82
+ ) -> None:
83
+ tools = create_tools(contract_no_source, adapter=adapter)
84
+ server = create_sdk_mcp_server(contract_no_source, tools=tools)
85
+ assert server is not None
86
+
87
+
88
+ def test_create_sdk_mcp_server_with_semantic_source(
89
+ contract_with_source: DataContract, adapter: DuckDBAdapter
90
+ ) -> None:
91
+ server = create_sdk_mcp_server(contract_with_source, adapter=adapter)
92
+ assert server is not None
93
+
94
+
95
+ def test_create_sdk_mcp_server_auto_creates_tools(
96
+ contract_no_source: DataContract,
97
+ ) -> None:
98
+ server = create_sdk_mcp_server(contract_no_source)
99
+ assert server is not None
100
+
101
+
102
+ def test_create_sdk_mcp_server_custom_name(
103
+ contract_no_source: DataContract,
104
+ ) -> None:
105
+ server = create_sdk_mcp_server(
106
+ contract_no_source,
107
+ server_name="my-server",
108
+ server_version="2.0.0",
109
+ )
110
+ assert server is not None
111
+
112
+
113
+ def test_top_level_import() -> None:
114
+ from agentic_data_contracts import create_sdk_mcp_server as fn
115
+
116
+ assert fn is not None
@@ -0,0 +1,76 @@
1
+ """Tests for tools with wildcard table resolution."""
2
+
3
+ import json
4
+
5
+ import pytest
6
+
7
+ from agentic_data_contracts.adapters.duckdb import DuckDBAdapter
8
+ from agentic_data_contracts.core.contract import DataContract
9
+ from agentic_data_contracts.core.schema import (
10
+ AllowedTable,
11
+ DataContractSchema,
12
+ SemanticConfig,
13
+ )
14
+ from agentic_data_contracts.tools.factory import create_tools
15
+
16
+
17
+ @pytest.fixture
18
+ def adapter() -> DuckDBAdapter:
19
+ db = DuckDBAdapter(":memory:")
20
+ db.connection.execute("""
21
+ CREATE SCHEMA IF NOT EXISTS analytics;
22
+ CREATE TABLE analytics.orders (id INTEGER, amount DECIMAL);
23
+ CREATE TABLE analytics.customers (id INTEGER, name VARCHAR);
24
+ INSERT INTO analytics.orders VALUES (1, 100.00);
25
+ """)
26
+ return db
27
+
28
+
29
+ @pytest.fixture
30
+ def wildcard_contract() -> DataContract:
31
+ schema = DataContractSchema(
32
+ name="test",
33
+ semantic=SemanticConfig(
34
+ allowed_tables=[
35
+ AllowedTable.model_validate({"schema": "analytics", "tables": ["*"]}),
36
+ ],
37
+ ),
38
+ )
39
+ return DataContract(schema)
40
+
41
+
42
+ @pytest.mark.asyncio
43
+ async def test_list_tables_after_wildcard_resolve(
44
+ wildcard_contract: DataContract, adapter: DuckDBAdapter
45
+ ) -> None:
46
+ tools = create_tools(wildcard_contract, adapter=adapter)
47
+ tool = next(t for t in tools if t.name == "list_tables")
48
+ result = await tool.callable({})
49
+ text = result["content"][0]["text"]
50
+ data = json.loads(text)
51
+ table_names = [t["table"] for t in data["tables"]]
52
+ assert "orders" in table_names
53
+ assert "customers" in table_names
54
+
55
+
56
+ @pytest.mark.asyncio
57
+ async def test_run_query_with_wildcard_tables(
58
+ wildcard_contract: DataContract, adapter: DuckDBAdapter
59
+ ) -> None:
60
+ tools = create_tools(wildcard_contract, adapter=adapter)
61
+ tool = next(t for t in tools if t.name == "run_query")
62
+ result = await tool.callable({"sql": "SELECT id, amount FROM analytics.orders"})
63
+ text = result["content"][0]["text"]
64
+ assert "100" in text
65
+
66
+
67
+ @pytest.mark.asyncio
68
+ async def test_validate_query_with_wildcard_tables(
69
+ wildcard_contract: DataContract, adapter: DuckDBAdapter
70
+ ) -> None:
71
+ tools = create_tools(wildcard_contract, adapter=adapter)
72
+ tool = next(t for t in tools if t.name == "validate_query")
73
+ # analytics.orders should be allowed after wildcard resolution
74
+ result = await tool.callable({"sql": "SELECT id FROM analytics.orders"})
75
+ text = result["content"][0]["text"]
76
+ assert "valid" in text.lower()
@@ -9,7 +9,7 @@ resolution-markers = [
9
9
 
10
10
  [[package]]
11
11
  name = "agentic-data-contracts"
12
- version = "0.2.1"
12
+ version = "0.2.4"
13
13
  source = { editable = "." }
14
14
  dependencies = [
15
15
  { name = "pydantic" },
@@ -57,7 +57,7 @@ snowflake = [
57
57
  requires-dist = [
58
58
  { name = "agentic-data-contracts", extras = ["agent-sdk", "agent-contracts", "bigquery", "snowflake", "postgres", "duckdb"], marker = "extra == 'all'" },
59
59
  { name = "ai-agent-contracts", marker = "extra == 'agent-contracts'", specifier = ">=0.2.0" },
60
- { name = "claude-agent-sdk", marker = "extra == 'agent-sdk'" },
60
+ { name = "claude-agent-sdk", marker = "extra == 'agent-sdk'", specifier = ">=0.1.52" },
61
61
  { name = "duckdb", marker = "extra == 'dev'" },
62
62
  { name = "duckdb", marker = "extra == 'duckdb'" },
63
63
  { name = "google-cloud-bigquery", marker = "extra == 'bigquery'" },
@@ -412,19 +412,19 @@ wheels = [
412
412
 
413
413
  [[package]]
414
414
  name = "claude-agent-sdk"
415
- version = "0.1.50"
415
+ version = "0.1.52"
416
416
  source = { registry = "https://pypi.org/simple" }
417
417
  dependencies = [
418
418
  { name = "anyio" },
419
419
  { name = "mcp" },
420
420
  ]
421
- sdist = { url = "https://files.pythonhosted.org/packages/24/eb/42a7027a02d3827c6e49f97375a00e6da4708f81295d9afa1a0009ce4abd/claude_agent_sdk-0.1.50.tar.gz", hash = "sha256:e15157792857ecb55274a71f08981efcfda2e169bee7894cbdc245d05ac43203", size = 99070, upload-time = "2026-03-20T23:00:58.646Z" }
421
+ sdist = { url = "https://files.pythonhosted.org/packages/b1/d7/a56b7ba793918e5cc557447b25f2d428e11d6f025b6c0c7b1e6e3fa373c0/claude_agent_sdk-0.1.52.tar.gz", hash = "sha256:c27f35d850521c7cff18448b38ff0dd5e899a4aeb6de9d28c0b2a66863eaf134", size = 116267, upload-time = "2026-03-29T02:40:25.554Z" }
422
422
  wheels = [
423
- { url = "https://files.pythonhosted.org/packages/b2/97/66bc98d5026dbed68b7469a4990de71d8c40d19713e37dafacf32ba3be3b/claude_agent_sdk-0.1.50-py3-none-macosx_11_0_arm64.whl", hash = "sha256:858b1822451209b2c3ad8df27458168d29ac19fd628680853f7707ea017fea73", size = 58223299, upload-time = "2026-03-20T23:01:01.742Z" },
424
- { url = "https://files.pythonhosted.org/packages/35/0d/65dda40016faa30a63a950d48b400ad26913e8e333e418651faf04d20673/claude_agent_sdk-0.1.50-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:44e75b9d076bd6030742729f99eb38777b80f052b22338d0a028d8190fc59e52", size = 61019645, upload-time = "2026-03-20T23:01:04.742Z" },
425
- { url = "https://files.pythonhosted.org/packages/ec/c0/e5c7c6b9e378553fe24bb5367caede725e274a494b6d126e719971c53b8b/claude_agent_sdk-0.1.50-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:7363d431dc6efd83fa658a045e14fa4357440352b548002bfb9096d8f04d143c", size = 74590847, upload-time = "2026-03-20T23:01:07.899Z" },
426
- { url = "https://files.pythonhosted.org/packages/3d/af/658a28cb070e0b59ac98e88411536f6f9b8d81e8ddde9a8340106b0b8b0f/claude_agent_sdk-0.1.50-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:493d8cc43f4166291606749cf47b03e822f03b7f371cc77af697564017ccf579", size = 75231505, upload-time = "2026-03-20T23:01:11.45Z" },
427
- { url = "https://files.pythonhosted.org/packages/41/44/ff1f2c137406392fa0a69e3c3ff37150267da664decddb6dee83b80ba162/claude_agent_sdk-0.1.50-py3-none-win_amd64.whl", hash = "sha256:2e44caf3e5bce56e26a18158acf3e1c2c2784cf8fa15e425afe92816c987eb1a", size = 75846174, upload-time = "2026-03-20T23:01:15.277Z" },
423
+ { url = "https://files.pythonhosted.org/packages/31/e0/6759f98c699ef5da84750b93de543a839f3e3b905b5ce20424a5033c58fa/claude_agent_sdk-0.1.52-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0f15c91319c20831f881fd4b6bcec1772a3599d66da5e7c057d79945bf603e1a", size = 57872944, upload-time = "2026-03-29T02:40:28.742Z" },
424
+ { url = "https://files.pythonhosted.org/packages/cd/e8/d3c3ef7cb104d3ebb6348a82eab7574b8c0eaddb683efd3f606f3c22bc21/claude_agent_sdk-0.1.52-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:f32ca2ca95e312678af63ee60a5fb1b765f98ed47825ea3ba90322ceb26736ff", size = 59615855, upload-time = "2026-03-29T02:40:31.765Z" },
425
+ { url = "https://files.pythonhosted.org/packages/f0/b7/2c4def1c5c67b9bad8966b8839d6d9da812a6458050650dbaba3526d3824/claude_agent_sdk-0.1.52-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:97c238fda13f0bea057e546895a3dc67468fc1acdbbc00d0b53a46cb3ba588dc", size = 71217617, upload-time = "2026-03-29T02:40:35.127Z" },
426
+ { url = "https://files.pythonhosted.org/packages/44/9e/4be542b58610bd74056dfed37dbdc5a12224a2fbde41a333ddbcd8255a8a/claude_agent_sdk-0.1.52-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:4cd3f2e6fd5b272b16114fbc8dcd4348cbe8615dcc1b3bea251a7d489bf83a5d", size = 71292911, upload-time = "2026-03-29T02:40:38.919Z" },
427
+ { url = "https://files.pythonhosted.org/packages/93/73/4601d43d57354bd6e664444970bd5c1de8003542745a4388cf84611c9f26/claude_agent_sdk-0.1.52-py3-none-win_amd64.whl", hash = "sha256:a8a5455d248b76c17126abee0f69d0f9870cbc0d52cb2f8ad5eb3deddb05af39", size = 73447753, upload-time = "2026-03-29T02:40:42.786Z" },
428
428
  ]
429
429
 
430
430
  [[package]]