toolframe-core 0.6.2__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.
- toolframe_core/__init__.py +3 -0
- toolframe_core/_state_postgres.py +420 -0
- toolframe_core/_state_sqlite.py +339 -0
- toolframe_core/access.py +513 -0
- toolframe_core/api.py +700 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/LICENSE +21 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/SKILL.md +59 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/agents/openai.yaml +4 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/LICENSE +21 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/pyproject.toml +21 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/__init__.py +1 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/docs/usage.md +13 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/migrations/0001_init.sql +5 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/page.py +25 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/plugin.py +119 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/service.py +48 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/references/design.md +60 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/references/operations.md +133 -0
- toolframe_core/bundled_skills/toolframe-plugin-design/references/plugin-api.md +186 -0
- toolframe_core/cli.py +681 -0
- toolframe_core/config.py +267 -0
- toolframe_core/diagnostics.py +200 -0
- toolframe_core/graph.py +207 -0
- toolframe_core/host.py +2610 -0
- toolframe_core/maintenance.py +868 -0
- toolframe_core/mcp_access.py +259 -0
- toolframe_core/network.py +97 -0
- toolframe_core/oidc_access.py +187 -0
- toolframe_core/packages.py +728 -0
- toolframe_core/process.py +212 -0
- toolframe_core/redaction.py +120 -0
- toolframe_core/scaffold.py +142 -0
- toolframe_core/skills.py +210 -0
- toolframe_core/state.py +226 -0
- toolframe_core/state_admin.py +270 -0
- toolframe_core/stores.py +88 -0
- toolframe_core/templates.json +6 -0
- toolframe_core/templates.py +56 -0
- toolframe_core-0.6.2.dist-info/METADATA +465 -0
- toolframe_core-0.6.2.dist-info/RECORD +46 -0
- toolframe_core-0.6.2.dist-info/WHEEL +4 -0
- toolframe_core-0.6.2.dist-info/entry_points.txt +2 -0
- toolframe_core-0.6.2.dist-info/licenses/LICENSE +202 -0
- toolframe_core-0.6.2.dist-info/licenses/NOTICE +14 -0
- toolframe_core-0.6.2.dist-info/licenses/src/toolframe_core/bundled_skills/toolframe-plugin-design/LICENSE +21 -0
- toolframe_core-0.6.2.dist-info/licenses/src/toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/LICENSE +21 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Public plugin contract
|
|
2
|
+
|
|
3
|
+
Target: `toolframe-sdk==0.3.0`, Core 0.6.1, Python 3.14. Use this packaged reference
|
|
4
|
+
and the starter when the source workspace is unavailable. If the installed SDK
|
|
5
|
+
version differs, inspect its public definitions before adapting these examples.
|
|
6
|
+
|
|
7
|
+
## Package and service
|
|
8
|
+
|
|
9
|
+
One wheel provides one plugin, with one instance per application. A business
|
|
10
|
+
plugin depends on `toolframe-sdk`, not core or provider implementation modules.
|
|
11
|
+
The entry-point group is `toolframe.plugins`; its name and `PluginDefinition.id`
|
|
12
|
+
should be the same stable, lowercase hyphenated ID.
|
|
13
|
+
|
|
14
|
+
```toml
|
|
15
|
+
[project.entry-points."toolframe.plugins"]
|
|
16
|
+
sample-notes = "toolframe_sample_notes.plugin:plugin"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`plugin` is a `PluginDefinition`, not a factory function. It declares:
|
|
20
|
+
|
|
21
|
+
- `factory="package.module:Class"`: a subclass of `toolframe_sdk.Plugin`.
|
|
22
|
+
- `services={name: ServiceSpec(version=1, methods={...})}`: named business APIs.
|
|
23
|
+
- `MethodSpec(audience="business", required_scopes=("sample-notes.read",), input_schema=..., output_schema=..., read_only=...)`: JSON schemas
|
|
24
|
+
for each method. Use bounded inputs and explicit object properties. JSON Schema
|
|
25
|
+
defaults are documentation; implement actual Python defaults too.
|
|
26
|
+
- `requires={alias: Requirement(service=name, version=1, optional=False)}`:
|
|
27
|
+
consumed services. In the worker, call
|
|
28
|
+
`await self.context.call(alias, method, params)`.
|
|
29
|
+
- `config_schema`: accepted configuration; read values from `self.context.config`.
|
|
30
|
+
|
|
31
|
+
The host calls `await plugin.invoke(service, method, params)`. Dispatch only
|
|
32
|
+
declared methods and return JSON values matching the output schema. Signal
|
|
33
|
+
business failures with `PluginError(code, message, details)`. Use stable error
|
|
34
|
+
codes that pages and tools can interpret; avoid leaking credentials in details.
|
|
35
|
+
|
|
36
|
+
Keep module import and definitions free of side effects. `activate()` prepares
|
|
37
|
+
runtime resources, `health()` reports readiness, and `dispose()` releases owned
|
|
38
|
+
resources and awaits `super().dispose()`. Use `self.context.task(factory)` for
|
|
39
|
+
owned asynchronous work; account for cancellation. Each plugin runs in a separate
|
|
40
|
+
process. Cross-plugin Python objects and imports are not the service boundary.
|
|
41
|
+
|
|
42
|
+
## Authorization and publication
|
|
43
|
+
|
|
44
|
+
Every definition requires `authorization=AuthorizationSpec(scopes={...})`, even
|
|
45
|
+
infrastructure plugins with an empty catalog. Legacy definitions fail validation;
|
|
46
|
+
no whole-plugin fallback permission is invented. Each scope ID starts with the
|
|
47
|
+
owning plugin ID, for example `sample-notes.read` and `sample-notes.write`:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
authorization = AuthorizationSpec(
|
|
51
|
+
scopes={
|
|
52
|
+
"sample-notes.read": ScopeSpec(title="Read notes"),
|
|
53
|
+
"sample-notes.write": ScopeSpec(title="Capture notes"),
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Business methods declare `audience="business"` and `required_scopes`. Internal
|
|
59
|
+
dependency services declare `audience="internal"` and have no visitor scopes;
|
|
60
|
+
they cannot be invoked directly by Dashboard/API clients. Managed SQL uses this
|
|
61
|
+
internal audience and the caller plugin's storage ownership. Do not require the
|
|
62
|
+
visitor to hold storage administration permission merely to submit a form.
|
|
63
|
+
|
|
64
|
+
Dashboard pages declare `Resource.required_scopes`. `context.can("sample-notes.write")`
|
|
65
|
+
can disable editing controls. The host-issued context is supplied by the provider;
|
|
66
|
+
page code does not construct principals or handle browser passwords. Nested
|
|
67
|
+
service calls keep the admitted caller and check the callee's declared scopes.
|
|
68
|
+
|
|
69
|
+
`read_only=True` describes mutation behavior; it does not make a method public.
|
|
70
|
+
Use `public_api=True` only for deliberately public business methods, served at
|
|
71
|
+
`POST /api/PLUGIN/SERVICE/METHOD`. This HTTP route and llms documents are public
|
|
72
|
+
independently of Dashboard passwords. Leave sensitive methods `public_api=False`
|
|
73
|
+
and keep private content out of published documents.
|
|
74
|
+
|
|
75
|
+
| Entrance | Required decision |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| Dashboard and `/_toolframe/v1/services/...` | Set `access.mode="protected"` and `access.failure_mode="closed"` for confidential data; enforce method scopes. |
|
|
78
|
+
| `/api/...` | Only explicitly public business methods are reachable; Dashboard/MCP credentials do not protect them. |
|
|
79
|
+
| `/mcp` | Independent `mcp_access` policy; public by default, or a healthy MCP auth provider and scoped MCP credentials in protected mode. |
|
|
80
|
+
| llms documents | Public; publish only intentionally shareable content. |
|
|
81
|
+
| Local control / management | Direct loopback control and the management token are operator recovery paths; remote system calls require system authority. |
|
|
82
|
+
|
|
83
|
+
An MCP tool resource publishes a business method to the MCP catalog, whose policy
|
|
84
|
+
is independent of Dashboard login. For confidential tools, install/enable a
|
|
85
|
+
compatible `toolframe-mcp-auth` provider and set:
|
|
86
|
+
|
|
87
|
+
```toml
|
|
88
|
+
[access]
|
|
89
|
+
mode = "protected"
|
|
90
|
+
failure_mode = "closed"
|
|
91
|
+
|
|
92
|
+
[mcp_access]
|
|
93
|
+
mode = "protected"
|
|
94
|
+
auth_provider = "mcp-auth"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Use business-scoped MCP keys or configured delegated user grants; Dashboard
|
|
98
|
+
passwords and management tokens are not MCP keys. Protected MCP stays unavailable
|
|
99
|
+
if its verifier is absent or fails, independently of Dashboard fallback. New Core
|
|
100
|
+
applications write `failure_mode="closed"`; older files omitting it retain legacy
|
|
101
|
+
public fallback. Review existing choices before changing them. The access-password
|
|
102
|
+
page shows entrance states; its failure-policy editor requires Core 0.6.1.
|
|
103
|
+
|
|
104
|
+
Scopes authorize methods, not individual rows. Password labels identify grants,
|
|
105
|
+
not people. Per-person or tenant data needs a trusted identity and explicit
|
|
106
|
+
business-layer row filtering. Never use a caller-supplied label as identity.
|
|
107
|
+
System/internal methods cannot be published as public APIs or MCP tools. The
|
|
108
|
+
starter deliberately publishes a public read-only query; remove that flag when
|
|
109
|
+
adapting it for private data.
|
|
110
|
+
|
|
111
|
+
Page `service.call(..., activity=False)` is for background polling, so it does not
|
|
112
|
+
extend a session's idle deadline or last-use timestamp. Interactive calls default
|
|
113
|
+
to activity. Every new call rechecks current access; already admitted call trees
|
|
114
|
+
retain their snapshot if a password is subsequently revoked. Plugin workers are
|
|
115
|
+
trusted application code, not a sandbox for untrusted extensions.
|
|
116
|
+
|
|
117
|
+
## Managed SQLite
|
|
118
|
+
|
|
119
|
+
Declare `requires={"storage": Requirement(service="toolframe.storage.sql")}`
|
|
120
|
+
and `store=StoreSchema(revision="v1", migrations=(Migration(...),))` when needed.
|
|
121
|
+
The provider allocates a database for the stable plugin ID. It retains data when
|
|
122
|
+
a plugin is disabled, removed or reloaded. Core's own SQLite holds management
|
|
123
|
+
state and is not business storage.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
storage = self.context.storage() # requires alias "storage"
|
|
127
|
+
async with storage.transaction() as tx:
|
|
128
|
+
result = await tx.execute("INSERT INTO notes(body) VALUES (?)", (body,))
|
|
129
|
+
rows = await tx.query(
|
|
130
|
+
"SELECT id, body FROM notes WHERE id = ?", (result["lastrowid"],)
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`query()` returns a list of row dictionaries. `execute()` returns `rowcount` and
|
|
135
|
+
`lastrowid`; use a transaction plus a query to read a mutation's result, rather
|
|
136
|
+
than SQL `RETURNING`. Bind values as parameters. Put DDL and indexes in migrations.
|
|
137
|
+
The provider manages connections, transaction control and file paths; business
|
|
138
|
+
code does not use `sqlite3.connect`, `ATTACH`, `PRAGMA` or raw `BEGIN`/`COMMIT`.
|
|
139
|
+
Nested and cross-owner transactions are unsupported. Bound query results and
|
|
140
|
+
paginate; do not fetch an entire unbounded history over RPC.
|
|
141
|
+
|
|
142
|
+
Migrations form a linear history: the first has `parent=None`, each later one
|
|
143
|
+
names the preceding revision, and `StoreSchema.revision` is the final revision.
|
|
144
|
+
Read SQL via `importlib.resources.files` and ship it inside the wheel. Applied
|
|
145
|
+
revision names and SQL checksums are immutable. New empty stores initialize
|
|
146
|
+
automatically; existing-store changes use the explicit migration operation.
|
|
147
|
+
|
|
148
|
+
## Optional resources
|
|
149
|
+
|
|
150
|
+
Publish resources only for requested surfaces. Missing presentation providers
|
|
151
|
+
leave the business service usable. A required service such as storage still
|
|
152
|
+
blocks activation when absent.
|
|
153
|
+
|
|
154
|
+
| Kind | `Resource.data` |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| `dashboard.page` | `entrypoint="package.page:render"`, optional integer `order`. |
|
|
157
|
+
| `mcp.tool` | `service`, `method`, `description`, `input_schema`, `output_schema`. Keep the schemas consistent with the target method. |
|
|
158
|
+
| `llms.document` | `package` and relative `path` to packaged Markdown. |
|
|
159
|
+
|
|
160
|
+
Use a stable `Resource.id` unique within its kind. Resources belong to their
|
|
161
|
+
plugin generation and are withdrawn when it stops. MCP names are prefixed with
|
|
162
|
+
the plugin ID, for example `sample-notes__list_notes`. Set a query's service
|
|
163
|
+
`MethodSpec.read_only=True`; write methods remain explicit mutations. The first
|
|
164
|
+
MCP provider supports tools, not resources or prompts.
|
|
165
|
+
|
|
166
|
+
Dashboard pages are ordinary synchronous `render(context: PageContext)` functions:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
def render(context):
|
|
170
|
+
import streamlit as st
|
|
171
|
+
|
|
172
|
+
service = context.services.require("sample-notes.notes", version=1)
|
|
173
|
+
result = service.call("list_notes", {"limit": 20})
|
|
174
|
+
st.dataframe(result["notes"])
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Put Streamlit in the wheel's `ui` extra and import it in the page. The host resolves
|
|
178
|
+
page contributors together in the Dashboard environment; the business worker
|
|
179
|
+
does not need Streamlit. Page dependencies must coexist. Namespace widget keys
|
|
180
|
+
by plugin ID. Page code uses the public service facade and cannot open worker
|
|
181
|
+
database files. Reload may reset all Dashboard sessions, so retain durable data
|
|
182
|
+
through business services and surface stale-version conflicts to the user.
|
|
183
|
+
|
|
184
|
+
The llms provider publishes an index and the specifically declared documents.
|
|
185
|
+
Write those documents for application users and agents invoking its tools. Keep
|
|
186
|
+
private design notes, credentials and the authoring skill outside those assets.
|