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.
- ccp/__init__.py +97 -0
- ccp/adapters/__init__.py +5 -0
- ccp/adapters/fastapi.py +97 -0
- ccp/adapters/mcp.py +208 -0
- ccp/agent.py +66 -0
- ccp/audit.py +39 -0
- ccp/canonical.py +192 -0
- ccp/consumer.py +75 -0
- ccp/crypto.py +96 -0
- ccp/decision.py +81 -0
- ccp/errors.py +35 -0
- ccp/hosts.py +52 -0
- ccp/model.py +187 -0
- ccp/requester.py +66 -0
- ccp/runtime.py +200 -0
- ccp/schema.py +134 -0
- ccp/signing.py +14 -0
- ccp/state_machine.py +33 -0
- ccp/stores.py +166 -0
- ccp/stores_sqlite.py +442 -0
- ccp_protocol-0.4.0.dist-info/METADATA +256 -0
- ccp_protocol-0.4.0.dist-info/RECORD +24 -0
- ccp_protocol-0.4.0.dist-info/WHEEL +5 -0
- ccp_protocol-0.4.0.dist-info/top_level.txt +1 -0
|
@@ -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 @@
|
|
|
1
|
+
ccp
|