ccp-protocol 0.4.0__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,256 @@
1
+ Metadata-Version: 2.1
2
+ Name: ccp-protocol
3
+ Version: 0.4.0
4
+ Summary: Reference runtime and SDK for the Capability Contacts Protocol
5
+ License: Apache-2.0
6
+ Keywords: capability,security,protocol,ed25519,ai-agents
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Topic :: Security
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: cryptography >=41
15
+ Provides-Extra: dev
16
+ Requires-Dist: fastapi >=0.110 ; extra == 'dev'
17
+ Requires-Dist: httpx >=0.27 ; extra == 'dev'
18
+ Requires-Dist: mcp >=2 ; extra == 'dev'
19
+ Provides-Extra: fastapi
20
+ Requires-Dist: fastapi >=0.110 ; extra == 'fastapi'
21
+ Provides-Extra: mcp
22
+ Requires-Dist: mcp >=2 ; extra == 'mcp'
23
+
24
+ # CCP reference SDK
25
+
26
+ A Python reference runtime for the Capability Contacts Protocol (draft
27
+ v0.4). It implements the protocol primitives and a runtime that validates
28
+ signals, enforces local grants, mediates host functions, supports
29
+ two-phase confirmation, and keeps a private audit log.
30
+
31
+ ## Layout
32
+
33
+ | Module | Implements |
34
+ | --- | --- |
35
+ | `ccp.canonical` | strict canonical JSON (§25.1) |
36
+ | `ccp.crypto` | Ed25519 key/signature encoding (§25.2) |
37
+ | `ccp.schema` | JSON Schema subset (§25.3) |
38
+ | `ccp.model` | `Manifest`, `Grant`, `Signal`, `Decision` |
39
+ | `ccp.requester` | build and sign manifests and signals |
40
+ | `ccp.consumer` | verify inbound documents, permission cards |
41
+ | `ccp.agent` | agent tool calls and attenuation |
42
+ | `ccp.stores` | grant/manifest/schema stores, replay, limits |
43
+ | `ccp.decision` | the pure §10.3 validation decision |
44
+ | `ccp.state_machine` | the §26.3 side-effect transitions |
45
+ | `ccp.hosts` | host-function registry and privilege mediation |
46
+ | `ccp.runtime` | submission, confirmation, execution, audit |
47
+ | `ccp.adapters.fastapi` | provider signal/manifest routes |
48
+ | `ccp.adapters.mcp` | transport-neutral MCP tool bridge |
49
+
50
+ ## Quick start
51
+
52
+ ```python
53
+ from ccp import (Grant, GrantStore, HostRegistry, Manifest, ManifestStore,
54
+ Runtime, SchemaRegistry, SigningKey, sign_document)
55
+
56
+ provider = SigningKey.generate()
57
+ requester = SigningKey.generate()
58
+
59
+ manifest = Manifest.parse(sign_document({
60
+ "ccp": "0.4-draft",
61
+ "provider": {"id": provider.key_id, "name": "Example Service"},
62
+ "version": 1,
63
+ "intents": {
64
+ "example.lookup": {
65
+ "params_schema": "schema:example.lookup.params.v1",
66
+ "result_schema": "schema:example.lookup.result.v1",
67
+ "required_capabilities": ["store.read"],
68
+ "confirmation": "grant-policy",
69
+ }
70
+ },
71
+ }, provider))
72
+ manifest.verify()
73
+
74
+ grant = Grant(
75
+ id="g-1",
76
+ requester_key=requester.key_id,
77
+ contact_id="c-1",
78
+ manifest_digest=manifest.digest,
79
+ manifest_version=1,
80
+ intents=["example.lookup"],
81
+ limits={"calls_per_hour": 10},
82
+ created_at="2026-10-22T10:00:00Z",
83
+ expires_at="2026-10-22T23:00:00Z",
84
+ confirmation={"example.lookup": "none"},
85
+ capabilities=["store.read"],
86
+ placement=["local"],
87
+ egress={"network": False},
88
+ )
89
+
90
+ schemas = SchemaRegistry({
91
+ "schema:example.lookup.params.v1": {
92
+ "type": "object",
93
+ "properties": {"item_id": {"type": "string"}},
94
+ "required": ["item_id"],
95
+ "additionalProperties": False,
96
+ },
97
+ })
98
+ hosts = HostRegistry()
99
+ hosts.register_host("store.read", lambda item_id: {"item_id": item_id})
100
+ hosts.register_intent(
101
+ "example.lookup",
102
+ lambda params, access: access.call("store.read", params["item_id"]),
103
+ )
104
+
105
+ runtime = Runtime(
106
+ grants=GrantStore([grant]),
107
+ manifests=ManifestStore([manifest]),
108
+ schemas=schemas,
109
+ hosts=hosts,
110
+ clock=lambda: "2026-10-22T12:00:30Z",
111
+ )
112
+
113
+ signal = sign_document({
114
+ "ccp": "0.4-draft",
115
+ "request_id": "req-1",
116
+ "grant_digest": manifest.digest,
117
+ "intent": "example.lookup",
118
+ "params": {"item_id": "item-123"},
119
+ "issued_at": "2026-10-22T12:00:00Z",
120
+ "expires_at": "2026-10-22T12:01:00Z",
121
+ }, requester)
122
+
123
+ decision = runtime.submit(signal, requester.verifying_key())
124
+ assert decision.authorized
125
+ ```
126
+
127
+ ## Confirmation
128
+
129
+ If a grant's confirmation policy for an intent is `per_action`,
130
+ `Runtime.submit` returns a `Decision` with `needs_confirmation` true and a
131
+ `pending_id`. The trusted client later calls:
132
+
133
+ ```python
134
+ runtime.confirm(decision.pending_id, approved=True) # or approved=False
135
+ ```
136
+
137
+ ## Roles
138
+
139
+ Requester, consumer, and agent mirror the TypeScript surfaces.
140
+
141
+ ```python
142
+ from ccp import Requester, SigningKey
143
+ from ccp.consumer import permission_card, verify_manifest
144
+
145
+ requester = Requester(SigningKey.generate())
146
+ manifest_wire = requester.manifest(
147
+ {"id": provider.key_id, "name": "Example Service"}, 1, intents)
148
+
149
+ manifest = verify_manifest(manifest_wire)
150
+ card = permission_card(manifest, "example.lookup")
151
+
152
+ signal_wire = requester.signal(
153
+ "req-1", manifest.digest, "example.lookup", {"item_id": "item-123"},
154
+ "2026-10-22T12:00:00Z", "2026-10-22T12:01:00Z")
155
+
156
+ from ccp import Agent, attenuates
157
+
158
+ agent = Agent(agent_key)
159
+ tool_signal = agent.signal_for(
160
+ intent="example.lookup", params={"item_id": "item-123"},
161
+ grant=grant, request_id="req-2",
162
+ issued_at="2026-10-22T12:00:00Z", expires_at="2026-10-22T12:01:00Z")
163
+ assert attenuates(parent_grant, child_grant)
164
+ ```
165
+
166
+ ## FastAPI provider
167
+
168
+ ```python
169
+ from fastapi import FastAPI
170
+ from ccp.adapters.fastapi import manifest_router, provider_router
171
+
172
+ app = FastAPI()
173
+ app.include_router(manifest_router(manifest.document))
174
+ app.include_router(provider_router(runtime, key_resolver))
175
+ ```
176
+
177
+ `key_resolver(request)` returns the sender's `VerifyingKey`, or `None` for an
178
+ unknown sender; it may be sync or async. Statuses match the TypeScript
179
+ adapter: 200 authorized/confirmation_required, 400 invalid_request, 401
180
+ unknown sender, 403 unauthenticated/no_matching_grant, 409 other refusals.
181
+
182
+ The adapter only parses `{"signal": "<canonical json>"}`, resolves the key,
183
+ and calls `Runtime.submit`. Install with `pip install 'ccp-protocol[fastapi]'`.
184
+
185
+ ## MCP agent bridge
186
+
187
+ ```python
188
+ from ccp.adapters.mcp import McpServer
189
+
190
+ bridge = McpServer(
191
+ runtime=runtime,
192
+ agent=agent,
193
+ grant=grant,
194
+ # Trusted host decision. Omit to surface confirmation_required instead.
195
+ confirm=lambda request: ask_user(request.card, request.params),
196
+ )
197
+
198
+ # Adapt bridge.tools and `await bridge.call_tool(name, args)` in your MCP
199
+ # server. The bridge has no MCP dependency and never auto-approves.
200
+ ```
201
+
202
+ One bridge serves one grant; tool names are the granted intent names.
203
+ Provider descriptions are labelled untrusted, results are returned as JSON
204
+ data, and confirmation is the host's decision. Install with
205
+ `pip install 'ccp-protocol[mcp]'`. See `examples/agent_mcp/`.
206
+
207
+ ## Durable stores
208
+
209
+ `Runtime` accepts any object implementing the store contracts in
210
+ `ccp/stores.py`. `ccp.stores_sqlite.SqliteStores` provides SQLite-backed
211
+ implementations of all six, using only the standard library:
212
+
213
+ ```python
214
+ from ccp import Runtime
215
+ from ccp.stores_sqlite import SqliteStores
216
+
217
+ stores = SqliteStores("ccp.db") # or ":memory:"
218
+ runtime = Runtime(
219
+ grants=stores.grants,
220
+ manifests=stores.manifests,
221
+ replay=stores.replay,
222
+ limits=stores.limits,
223
+ pending=stores.pending,
224
+ audit=stores.audit,
225
+ schemas=schemas,
226
+ hosts=hosts,
227
+ )
228
+ ```
229
+
230
+ The atomic operations (`try_reserve`, `mark_if_new`, `take`) use
231
+ `BEGIN IMMEDIATE` transactions, so they are safe across processes sharing
232
+ the database file. See `../../docs/guides/persistence.md` for the contract.
233
+
234
+ ## Examples
235
+
236
+ - `examples/provider_fastapi/` — HTTP provider (`python examples/provider_fastapi/server.py`)
237
+ - `examples/agent_mcp/` — stdio MCP server and in-memory client demo
238
+ (`python examples/agent_mcp/demo.py`)
239
+
240
+ ## Tests
241
+
242
+ ```sh
243
+ /usr/bin/python3 -m unittest discover -s tests
244
+ ```
245
+
246
+ The suite includes conformance tests that run the frozen vectors in
247
+ `../../test-vectors/` through the SDK, plus end-to-end runtime, role, and
248
+ adapter tests. The FastAPI and MCP tests need the `dev` extra:
249
+
250
+ ```sh
251
+ pip install '.[dev]'
252
+ /usr/bin/python3 -m unittest discover -s tests
253
+ ```
254
+
255
+ Requires Python 3.10+ and `cryptography` for Ed25519. The MCP extra
256
+ requires Python 3.10+.
@@ -0,0 +1,24 @@
1
+ ccp/__init__.py,sha256=wzJVryLYk29_n8hBVSmOSUZjqR7BtuJ3sT82uQ_6UC0,2186
2
+ ccp/agent.py,sha256=OHasJVM78yEYOsVEWeOmrw5Tbx8lFQ5eJe20x8q1ACU,2171
3
+ ccp/audit.py,sha256=8dxRYMXMorcSBO3IFA0Z6vK5lHa8xzfNF7FDaZnWX4s,1070
4
+ ccp/canonical.py,sha256=PQZMIFoMatvLObri4lgNwMB0jFuBpbNKwcM0TQy7iAM,5498
5
+ ccp/consumer.py,sha256=Jpxi51E-LUO3CAN9MNQPqNonfV3KgM-qpSMBXP7CImA,2431
6
+ ccp/crypto.py,sha256=JjypsPxS0pgW2mqrNk8B5SlD2iSHnjVo8FmWh_Z_8gI,2744
7
+ ccp/decision.py,sha256=N8XDPInfIppi0Z1D6feIf70p4ATpu6VSZIX_DEuIyT8,2749
8
+ ccp/errors.py,sha256=Cl6afRYnZpLmL93Zbp9CzrW72JfuStvovujmL2v8Q7o,1002
9
+ ccp/hosts.py,sha256=P-dqk8Gca2lXpculZ2h8zEUwiaBAl6gAdrHK0ylZ-hs,1543
10
+ ccp/model.py,sha256=7f_kHbJnqd0TPnJ271ZBa0pY03Pk6tk19UpOtn8qYF8,5829
11
+ ccp/requester.py,sha256=tSipgoQhXfaza_i8daFiA_RFbui6AmC8kuwfF6ErDio,2222
12
+ ccp/runtime.py,sha256=CfZDtw4IA78lo4_wl6jMJyZe6QlM1dGE1CMSbPssFk0,8950
13
+ ccp/schema.py,sha256=jniZV-5wxWx1W3GCVyP_o6QQFTaFjpL7COfUps9b1fA,5153
14
+ ccp/signing.py,sha256=R-yWggTKeiDTj6_gXLtrx5QaAT8oV8y1xvqs84Q36tg,438
15
+ ccp/state_machine.py,sha256=oBimrl8-vAPdc8yIkwADTZJ634lB5uOT0k4JgdW9s64,1163
16
+ ccp/stores.py,sha256=YkJCfI_cvd_DF9iV8XDASCDMvQxEbzxiJ5Lp6sMVyFU,4705
17
+ ccp/stores_sqlite.py,sha256=rAjbD-BsttpEVzE7SfSRcHkeL4lNHKjUMPaLoayXWXc,13639
18
+ ccp/adapters/__init__.py,sha256=aRUPMv51Vm79_vcJXsuopEhipoDvaqMlxSltLEYTZbc,182
19
+ ccp/adapters/fastapi.py,sha256=_2U8mQ5eAVVznYhgfeXIrV7yfji3oy_bmnxKFdLwrpA,2796
20
+ ccp/adapters/mcp.py,sha256=ZE5zUL200drZ-TE9L7HSvhr_Lm6SFniuwsicz4G_3nc,7510
21
+ ccp_protocol-0.4.0.dist-info/METADATA,sha256=v1oRK06w9fiMqwVdmdfT8heNPL7Cw1UEl7yvEDK-csQ,8176
22
+ ccp_protocol-0.4.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
23
+ ccp_protocol-0.4.0.dist-info/top_level.txt,sha256=b4nCwN1LL4U0qTIId_YvWlNgROJnqkRhItEASiR4iBU,4
24
+ ccp_protocol-0.4.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.42.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ ccp