modus-sdk 1.1.2__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.

Potentially problematic release.


This version of modus-sdk might be problematic. Click here for more details.

@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .eggs/
7
+ *.egg
8
+ .venv/
9
+ venv/
10
+ node_modules/
@@ -0,0 +1,296 @@
1
+ Metadata-Version: 2.4
2
+ Name: modus-sdk
3
+ Version: 1.1.2
4
+ Summary: Python SDK for the Modus REST API — manage agent passports, gates, and enforcement policies.
5
+ Project-URL: Homepage, https://modusoperator.com
6
+ Project-URL: Documentation, https://modusoperator.com/guide
7
+ Project-URL: Repository, https://github.com/StandardLogic/modus-mcp
8
+ Author-email: "Standard Logic Co." <support@modusoperator.com>
9
+ License-Expression: MIT
10
+ Keywords: ai-agents,mcp,modus,passports,permissions,trust
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: canonicaljson>=2.0.0
23
+ Requires-Dist: httpx>=0.25.0
24
+ Requires-Dist: pydantic>=2.0.0
25
+ Requires-Dist: pynacl>=1.5.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
28
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
29
+ Requires-Dist: respx>=0.21.0; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # modus-sdk
33
+
34
+ [![PyPI version](https://img.shields.io/pypi/v/modus-sdk)](https://pypi.org/project/modus-sdk/)
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
36
+
37
+ Python SDK for the [Modus](https://modusoperator.com) REST API. Manage agent passports, gates, and enforcement policies programmatically.
38
+
39
+ The MCP server (for Claude Desktop, Cursor, etc.) is the separate TypeScript package: [modus-mcp on npm](https://www.npmjs.com/package/modus-mcp).
40
+
41
+ ---
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install modus-sdk
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Usage
52
+
53
+ ```python
54
+ from modus import ModusClient
55
+
56
+ client = ModusClient(api_key="mod_live_xxx")
57
+
58
+ # List all gates
59
+ gates = client.list_gates()
60
+
61
+ # Create a gate
62
+ gate = client.create_gate(name="My API", gate_id="gate_my-api")
63
+
64
+ # Issue a passport
65
+ passport = client.issue_passport(
66
+ "gate_my-api",
67
+ agent_id="agent-001",
68
+ permissions=["read", "write"],
69
+ expires_in="7d",
70
+ )
71
+
72
+ # Check authorization
73
+ result = client.check_gate("gate_my-api", action="read", passport_id=passport["passport_id"])
74
+ print(result["allowed"]) # True
75
+
76
+ # Enforce with constraints
77
+ enforcement = client.enforce_action(
78
+ passport_id=passport["passport_id"],
79
+ action="write",
80
+ cost_cents=500,
81
+ )
82
+ print(enforcement["decision"]) # "PERMIT"
83
+
84
+ client.close()
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Async Usage
90
+
91
+ ```python
92
+ import asyncio
93
+ from modus import AsyncModusClient
94
+
95
+ async def main():
96
+ async with AsyncModusClient(api_key="mod_live_xxx") as client:
97
+ gates = await client.list_gates()
98
+ for gate in gates:
99
+ passports = await client.list_passports(gate["gate_id"])
100
+ print(f"{gate['name']}: {len(passports)} passports")
101
+
102
+ asyncio.run(main())
103
+ ```
104
+
105
+ Both clients support context managers for automatic cleanup:
106
+
107
+ ```python
108
+ with ModusClient(api_key="mod_live_xxx") as client:
109
+ gates = client.list_gates()
110
+ ```
111
+
112
+ ---
113
+
114
+ ## API Coverage
115
+
116
+ ### Gates
117
+
118
+ ```python
119
+ client.list_gates()
120
+ client.get_gate("gate_my-api")
121
+ client.create_gate(name="My API", gate_id="gate_my-api")
122
+ client.update_gate("gate_my-api", name="Updated Name")
123
+ client.delete_gate("gate_my-api")
124
+ ```
125
+
126
+ ### Passports
127
+
128
+ ```python
129
+ client.list_passports("gate_my-api")
130
+ client.get_passport("gate_my-api", "pp_xxx")
131
+ client.issue_passport("gate_my-api", agent_id="agent-001", permissions=["read"])
132
+ client.revoke_passport("gate_my-api", "pp_xxx")
133
+ client.reissue_passport("pp_xxx", accept_catalog_version=2)
134
+ ```
135
+
136
+ ### Attestations
137
+
138
+ ```python
139
+ client.list_attestations("gate_my-api", limit=50)
140
+ client.record_attestation("gate_my-api", passport_id="pp_xxx", permission="read", tool_name="search", result="allowed")
141
+ ```
142
+
143
+ ### Permission Catalog
144
+
145
+ ```python
146
+ client.get_catalog("gate_my-api")
147
+ client.create_catalog("gate_my-api", permissions=[...])
148
+ client.publish_catalog("gate_my-api", change_summary="Added search permission")
149
+ client.list_catalog_versions("gate_my-api")
150
+ client.get_catalog_version("gate_my-api", version=1)
151
+ client.get_catalog_impact("gate_my-api")
152
+ ```
153
+
154
+ ### Gate Check
155
+
156
+ ```python
157
+ client.check_gate("gate_my-api", action="read", passport_id="pp_xxx")
158
+ client.authorize_dry_run(gate_id="gate_my-api", passport={"..."}, requested_permission="read")
159
+ ```
160
+
161
+ ### Constraints
162
+
163
+ ```python
164
+ client.get_constraints("pp_xxx")
165
+ client.set_constraints("pp_xxx", {"read": {"core:rate:max_per_minute": 100}})
166
+ client.list_constraint_types(category="cost")
167
+ client.list_constraint_templates(category="security")
168
+ client.apply_constraint_template("pp_xxx", "conservative-agent")
169
+ client.create_constraint_template(slug="my-template", name="My Template", constraints={...})
170
+ ```
171
+
172
+ ### Enforcement (CEL)
173
+
174
+ ```python
175
+ client.enforce_action(passport_id="pp_xxx", action="write", cost_cents=500)
176
+ client.list_enforcement_attestations("pp_xxx", decision="BLOCK")
177
+ client.get_enforcement_attestation("enf_xxx")
178
+ client.verify_enforcement_attestation("enf_xxx")
179
+ ```
180
+
181
+ ### Anonymous Access
182
+
183
+ ```python
184
+ client.get_anonymous_policy("gate_my-api")
185
+ client.set_anonymous_policy("gate_my-api", enabled=True, allowed_actions=["read"])
186
+ client.get_anonymous_log("gate_my-api")
187
+ ```
188
+
189
+ ### Commerce
190
+
191
+ ```python
192
+ client.discover_services("flights:search", max_price_cents=100, sort="price_asc")
193
+ client.issue_consumption_attestation(passport_id="pp_xxx", gate_id="gate_my-api", action="flights:search", outcome="success")
194
+ client.generate_settlement(gate_id="gate_my-api", period_type="monthly", period_start="2025-01-01", period_end="2025-01-31")
195
+ client.list_settlements(gate_id="gate_my-api", status="pending")
196
+ client.get_settlement("stl_xxx")
197
+ client.update_settlement_status("stl_xxx", "invoiced")
198
+ client.get_sla_compliance("gate_my-api", period_start="2025-01-01", period_end="2025-01-31")
199
+ ```
200
+
201
+ ### Cumulative State
202
+
203
+ ```python
204
+ client.get_cumulative_state("pp_xxx")
205
+ client.reset_cumulative_state("pp_xxx", window_type="daily")
206
+ ```
207
+
208
+ ### API Keys
209
+
210
+ ```python
211
+ client.list_api_keys()
212
+ client.create_api_key(name="My Key", scopes=["gates:read", "passports:write"])
213
+ client.revoke_api_key("key_xxx")
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Cryptographic Verification
219
+
220
+ Local verification utilities for Ed25519 signatures and RFC 8785 content hashing:
221
+
222
+ ```python
223
+ from modus import verify_attestation_signature, verify_content_hash, compute_content_hash
224
+
225
+ valid = verify_attestation_signature(
226
+ attestation_json='{"gate_id":"gate_my-api",...}',
227
+ signature_b64="base64-sig...",
228
+ public_key_b64="base64-key...",
229
+ )
230
+
231
+ matches = verify_content_hash(catalog_snapshot, expected_hash)
232
+ hash_hex = compute_content_hash({"key": "value"})
233
+ ```
234
+
235
+ ---
236
+
237
+ ## Error Handling
238
+
239
+ ```python
240
+ from modus import (
241
+ ModusError,
242
+ AuthenticationError,
243
+ AuthorizationError,
244
+ NotFoundError,
245
+ RateLimitError,
246
+ ValidationError,
247
+ ConflictError,
248
+ )
249
+
250
+ try:
251
+ client.get_gate("gate_nonexistent")
252
+ except NotFoundError as e:
253
+ print(f"Not found: {e}")
254
+ print(f"Status: {e.status_code}") # 404
255
+ except RateLimitError as e:
256
+ print(f"Rate limited, retry after: {e.retry_after}s")
257
+ except ModusError as e:
258
+ print(f"API error: {e} (HTTP {e.status_code})")
259
+ ```
260
+
261
+ | Exception | HTTP Status |
262
+ |-----------|-------------|
263
+ | `AuthenticationError` | 401 |
264
+ | `AuthorizationError` | 403 |
265
+ | `NotFoundError` | 404 |
266
+ | `ConflictError` | 409 |
267
+ | `ValidationError` | 400, 422 |
268
+ | `RateLimitError` | 429 |
269
+ | `ModusError` | All other errors |
270
+
271
+ ---
272
+
273
+ ## Environment Variables
274
+
275
+ | Variable | Required | Default | Description |
276
+ |----------|----------|---------|-------------|
277
+ | `MODUS_API_KEY` | Yes | — | Your Modus API key |
278
+ | `MODUS_API_URL` | No | `https://modusoperator.com` | API base URL (override for local dev) |
279
+
280
+ You can also pass these directly to the client:
281
+
282
+ ```python
283
+ client = ModusClient(api_key="mod_live_xxx", base_url="http://localhost:3000")
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Documentation
289
+
290
+ Full documentation at [modusoperator.com/docs](https://modusoperator.com/docs)
291
+
292
+ ---
293
+
294
+ ## License
295
+
296
+ MIT — [Standard Logic Co.](https://standardlogic.ai)
@@ -0,0 +1,265 @@
1
+ # modus-sdk
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/modus-sdk)](https://pypi.org/project/modus-sdk/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+
6
+ Python SDK for the [Modus](https://modusoperator.com) REST API. Manage agent passports, gates, and enforcement policies programmatically.
7
+
8
+ The MCP server (for Claude Desktop, Cursor, etc.) is the separate TypeScript package: [modus-mcp on npm](https://www.npmjs.com/package/modus-mcp).
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pip install modus-sdk
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Usage
21
+
22
+ ```python
23
+ from modus import ModusClient
24
+
25
+ client = ModusClient(api_key="mod_live_xxx")
26
+
27
+ # List all gates
28
+ gates = client.list_gates()
29
+
30
+ # Create a gate
31
+ gate = client.create_gate(name="My API", gate_id="gate_my-api")
32
+
33
+ # Issue a passport
34
+ passport = client.issue_passport(
35
+ "gate_my-api",
36
+ agent_id="agent-001",
37
+ permissions=["read", "write"],
38
+ expires_in="7d",
39
+ )
40
+
41
+ # Check authorization
42
+ result = client.check_gate("gate_my-api", action="read", passport_id=passport["passport_id"])
43
+ print(result["allowed"]) # True
44
+
45
+ # Enforce with constraints
46
+ enforcement = client.enforce_action(
47
+ passport_id=passport["passport_id"],
48
+ action="write",
49
+ cost_cents=500,
50
+ )
51
+ print(enforcement["decision"]) # "PERMIT"
52
+
53
+ client.close()
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Async Usage
59
+
60
+ ```python
61
+ import asyncio
62
+ from modus import AsyncModusClient
63
+
64
+ async def main():
65
+ async with AsyncModusClient(api_key="mod_live_xxx") as client:
66
+ gates = await client.list_gates()
67
+ for gate in gates:
68
+ passports = await client.list_passports(gate["gate_id"])
69
+ print(f"{gate['name']}: {len(passports)} passports")
70
+
71
+ asyncio.run(main())
72
+ ```
73
+
74
+ Both clients support context managers for automatic cleanup:
75
+
76
+ ```python
77
+ with ModusClient(api_key="mod_live_xxx") as client:
78
+ gates = client.list_gates()
79
+ ```
80
+
81
+ ---
82
+
83
+ ## API Coverage
84
+
85
+ ### Gates
86
+
87
+ ```python
88
+ client.list_gates()
89
+ client.get_gate("gate_my-api")
90
+ client.create_gate(name="My API", gate_id="gate_my-api")
91
+ client.update_gate("gate_my-api", name="Updated Name")
92
+ client.delete_gate("gate_my-api")
93
+ ```
94
+
95
+ ### Passports
96
+
97
+ ```python
98
+ client.list_passports("gate_my-api")
99
+ client.get_passport("gate_my-api", "pp_xxx")
100
+ client.issue_passport("gate_my-api", agent_id="agent-001", permissions=["read"])
101
+ client.revoke_passport("gate_my-api", "pp_xxx")
102
+ client.reissue_passport("pp_xxx", accept_catalog_version=2)
103
+ ```
104
+
105
+ ### Attestations
106
+
107
+ ```python
108
+ client.list_attestations("gate_my-api", limit=50)
109
+ client.record_attestation("gate_my-api", passport_id="pp_xxx", permission="read", tool_name="search", result="allowed")
110
+ ```
111
+
112
+ ### Permission Catalog
113
+
114
+ ```python
115
+ client.get_catalog("gate_my-api")
116
+ client.create_catalog("gate_my-api", permissions=[...])
117
+ client.publish_catalog("gate_my-api", change_summary="Added search permission")
118
+ client.list_catalog_versions("gate_my-api")
119
+ client.get_catalog_version("gate_my-api", version=1)
120
+ client.get_catalog_impact("gate_my-api")
121
+ ```
122
+
123
+ ### Gate Check
124
+
125
+ ```python
126
+ client.check_gate("gate_my-api", action="read", passport_id="pp_xxx")
127
+ client.authorize_dry_run(gate_id="gate_my-api", passport={"..."}, requested_permission="read")
128
+ ```
129
+
130
+ ### Constraints
131
+
132
+ ```python
133
+ client.get_constraints("pp_xxx")
134
+ client.set_constraints("pp_xxx", {"read": {"core:rate:max_per_minute": 100}})
135
+ client.list_constraint_types(category="cost")
136
+ client.list_constraint_templates(category="security")
137
+ client.apply_constraint_template("pp_xxx", "conservative-agent")
138
+ client.create_constraint_template(slug="my-template", name="My Template", constraints={...})
139
+ ```
140
+
141
+ ### Enforcement (CEL)
142
+
143
+ ```python
144
+ client.enforce_action(passport_id="pp_xxx", action="write", cost_cents=500)
145
+ client.list_enforcement_attestations("pp_xxx", decision="BLOCK")
146
+ client.get_enforcement_attestation("enf_xxx")
147
+ client.verify_enforcement_attestation("enf_xxx")
148
+ ```
149
+
150
+ ### Anonymous Access
151
+
152
+ ```python
153
+ client.get_anonymous_policy("gate_my-api")
154
+ client.set_anonymous_policy("gate_my-api", enabled=True, allowed_actions=["read"])
155
+ client.get_anonymous_log("gate_my-api")
156
+ ```
157
+
158
+ ### Commerce
159
+
160
+ ```python
161
+ client.discover_services("flights:search", max_price_cents=100, sort="price_asc")
162
+ client.issue_consumption_attestation(passport_id="pp_xxx", gate_id="gate_my-api", action="flights:search", outcome="success")
163
+ client.generate_settlement(gate_id="gate_my-api", period_type="monthly", period_start="2025-01-01", period_end="2025-01-31")
164
+ client.list_settlements(gate_id="gate_my-api", status="pending")
165
+ client.get_settlement("stl_xxx")
166
+ client.update_settlement_status("stl_xxx", "invoiced")
167
+ client.get_sla_compliance("gate_my-api", period_start="2025-01-01", period_end="2025-01-31")
168
+ ```
169
+
170
+ ### Cumulative State
171
+
172
+ ```python
173
+ client.get_cumulative_state("pp_xxx")
174
+ client.reset_cumulative_state("pp_xxx", window_type="daily")
175
+ ```
176
+
177
+ ### API Keys
178
+
179
+ ```python
180
+ client.list_api_keys()
181
+ client.create_api_key(name="My Key", scopes=["gates:read", "passports:write"])
182
+ client.revoke_api_key("key_xxx")
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Cryptographic Verification
188
+
189
+ Local verification utilities for Ed25519 signatures and RFC 8785 content hashing:
190
+
191
+ ```python
192
+ from modus import verify_attestation_signature, verify_content_hash, compute_content_hash
193
+
194
+ valid = verify_attestation_signature(
195
+ attestation_json='{"gate_id":"gate_my-api",...}',
196
+ signature_b64="base64-sig...",
197
+ public_key_b64="base64-key...",
198
+ )
199
+
200
+ matches = verify_content_hash(catalog_snapshot, expected_hash)
201
+ hash_hex = compute_content_hash({"key": "value"})
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Error Handling
207
+
208
+ ```python
209
+ from modus import (
210
+ ModusError,
211
+ AuthenticationError,
212
+ AuthorizationError,
213
+ NotFoundError,
214
+ RateLimitError,
215
+ ValidationError,
216
+ ConflictError,
217
+ )
218
+
219
+ try:
220
+ client.get_gate("gate_nonexistent")
221
+ except NotFoundError as e:
222
+ print(f"Not found: {e}")
223
+ print(f"Status: {e.status_code}") # 404
224
+ except RateLimitError as e:
225
+ print(f"Rate limited, retry after: {e.retry_after}s")
226
+ except ModusError as e:
227
+ print(f"API error: {e} (HTTP {e.status_code})")
228
+ ```
229
+
230
+ | Exception | HTTP Status |
231
+ |-----------|-------------|
232
+ | `AuthenticationError` | 401 |
233
+ | `AuthorizationError` | 403 |
234
+ | `NotFoundError` | 404 |
235
+ | `ConflictError` | 409 |
236
+ | `ValidationError` | 400, 422 |
237
+ | `RateLimitError` | 429 |
238
+ | `ModusError` | All other errors |
239
+
240
+ ---
241
+
242
+ ## Environment Variables
243
+
244
+ | Variable | Required | Default | Description |
245
+ |----------|----------|---------|-------------|
246
+ | `MODUS_API_KEY` | Yes | — | Your Modus API key |
247
+ | `MODUS_API_URL` | No | `https://modusoperator.com` | API base URL (override for local dev) |
248
+
249
+ You can also pass these directly to the client:
250
+
251
+ ```python
252
+ client = ModusClient(api_key="mod_live_xxx", base_url="http://localhost:3000")
253
+ ```
254
+
255
+ ---
256
+
257
+ ## Documentation
258
+
259
+ Full documentation at [modusoperator.com/docs](https://modusoperator.com/docs)
260
+
261
+ ---
262
+
263
+ ## License
264
+
265
+ MIT — [Standard Logic Co.](https://standardlogic.ai)
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "modus-sdk"
7
+ version = "1.1.2"
8
+ description = "Python SDK for the Modus REST API — manage agent passports, gates, and enforcement policies."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "Standard Logic Co.", email = "support@modusoperator.com" },
14
+ ]
15
+ keywords = [
16
+ "modus",
17
+ "ai-agents",
18
+ "permissions",
19
+ "passports",
20
+ "trust",
21
+ "mcp",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 4 - Beta",
25
+ "Intended Audience :: Developers",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.9",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Programming Language :: Python :: 3.13",
33
+ "Typing :: Typed",
34
+ ]
35
+ dependencies = [
36
+ "httpx>=0.25.0",
37
+ "pydantic>=2.0.0",
38
+ "pynacl>=1.5.0",
39
+ "canonicaljson>=2.0.0",
40
+ ]
41
+
42
+ [project.optional-dependencies]
43
+ dev = [
44
+ "pytest>=7.0.0",
45
+ "pytest-asyncio>=0.21.0",
46
+ "respx>=0.21.0",
47
+ ]
48
+
49
+ [project.urls]
50
+ Homepage = "https://modusoperator.com"
51
+ Documentation = "https://modusoperator.com/guide"
52
+ Repository = "https://github.com/StandardLogic/modus-mcp"
53
+
54
+ [tool.pytest.ini_options]
55
+ asyncio_mode = "auto"
56
+
57
+ [tool.hatch.build.targets.wheel]
58
+ packages = ["src/modus"]