docket-client 0.3.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.
- docket/__init__.py +8 -0
- docket/api/__init__.py +1 -0
- docket/api/access/__init__.py +1 -0
- docket/api/access/add_principal.py +193 -0
- docket/api/access/archive_role_assignment.py +182 -0
- docket/api/access/create_api_token.py +198 -0
- docket/api/access/create_application_principal.py +188 -0
- docket/api/access/create_role_assignment.py +194 -0
- docket/api/access/list_api_tokens.py +184 -0
- docket/api/access/list_principals.py +184 -0
- docket/api/access/list_role_assignments.py +200 -0
- docket/api/access/revoke_api_token.py +173 -0
- docket/api/identity/__init__.py +1 -0
- docket/api/identity/create_my_api_token.py +206 -0
- docket/api/identity/get_me.py +127 -0
- docket/api/identity/list_my_api_tokens.py +151 -0
- docket/api/identity/revoke_my_api_token.py +178 -0
- docket/api/recurring_acquisitions/__init__.py +1 -0
- docket/api/recurring_acquisitions/archive_recurring_acquisition.py +207 -0
- docket/api/recurring_acquisitions/create_recurring_acquisition.py +218 -0
- docket/api/recurring_acquisitions/get_recurring_acquisition.py +163 -0
- docket/api/recurring_acquisitions/list_recurring_acquisitions.py +248 -0
- docket/api/recurring_acquisitions/preview_recurring_acquisition.py +187 -0
- docket/api/recurring_acquisitions/preview_split_recurring_acquisition.py +232 -0
- docket/api/recurring_acquisitions/split_recurring_acquisition.py +258 -0
- docket/api/runs/__init__.py +1 -0
- docket/api/runs/get_run.py +160 -0
- docket/api/runs/list_runs.py +180 -0
- docket/api/runs/report_run_outcome.py +206 -0
- docket/api/scheduled_acquisitions/__init__.py +1 -0
- docket/api/scheduled_acquisitions/archive_scheduled_acquisition.py +203 -0
- docket/api/scheduled_acquisitions/create_scheduled_acquisition.py +185 -0
- docket/api/scheduled_acquisitions/get_resolved_settings.py +169 -0
- docket/api/scheduled_acquisitions/get_scheduled_acquisition.py +159 -0
- docket/api/scheduled_acquisitions/list_active_acquisitions.py +297 -0
- docket/api/scheduled_acquisitions/list_runs_for_scheduled_acquisition.py +191 -0
- docket/api/scheduled_acquisitions/list_scheduled_acquisitions.py +332 -0
- docket/api/scheduled_acquisitions/preview_scheduled_acquisition_edit.py +224 -0
- docket/api/scheduled_acquisitions/set_planning_status.py +199 -0
- docket/api/scheduled_acquisitions/start_run.py +221 -0
- docket/api/scheduled_acquisitions/update_scheduled_acquisition.py +226 -0
- docket/api/settings/__init__.py +1 -0
- docket/api/settings/list_behavior_curriculum_discriminator_values.py +188 -0
- docket/api/settings/list_behavior_curriculum_settings.py +199 -0
- docket/api/settings/list_fip_discriminator_values.py +184 -0
- docket/api/settings/list_fip_settings.py +195 -0
- docket/api/settings/list_live_behavior_curriculum_settings.py +233 -0
- docket/api/settings/list_live_fip_settings.py +225 -0
- docket/api/settings/preview_behavior_curriculum_settings.py +191 -0
- docket/api/settings/preview_fip_settings.py +177 -0
- docket/api/settings/push_behavior_curriculum_settings.py +222 -0
- docket/api/settings/push_fip_settings.py +206 -0
- docket/api/templates/__init__.py +1 -0
- docket/api/templates/archive_template.py +203 -0
- docket/api/templates/create_template.py +206 -0
- docket/api/templates/get_template.py +159 -0
- docket/api/templates/list_templates.py +174 -0
- docket/api/templates/update_template.py +234 -0
- docket/api/vocabulary/__init__.py +1 -0
- docket/api/vocabulary/list_instruments.py +165 -0
- docket/api/vocabulary/list_persons.py +132 -0
- docket/api/vocabulary/list_projects.py +132 -0
- docket/api/vocabulary/list_subjects.py +132 -0
- docket/api/vocabulary/lookup_person.py +166 -0
- docket/api/vocabulary/lookup_subject.py +166 -0
- docket/api/vocabulary/sync_vocabulary.py +148 -0
- docket/client.py +272 -0
- docket/errors.py +16 -0
- docket/models/__init__.py +221 -0
- docket/models/access_role.py +12 -0
- docket/models/access_role_nullable_type_1.py +12 -0
- docket/models/access_role_nullable_type_2_type_1.py +12 -0
- docket/models/access_role_nullable_type_3_type_1.py +12 -0
- docket/models/acquisition_variable.py +72 -0
- docket/models/acquisition_warning.py +126 -0
- docket/models/add_principal_request.py +83 -0
- docket/models/api_token_response.py +271 -0
- docket/models/api_token_status.py +10 -0
- docket/models/archive_recurring_acquisition_request.py +77 -0
- docket/models/archive_recurring_acquisition_response.py +83 -0
- docket/models/archive_scheduled_acquisition_request.py +78 -0
- docket/models/archive_template_request.py +66 -0
- docket/models/auth_mode.py +10 -0
- docket/models/behavior_curriculum_push_settings_request.py +85 -0
- docket/models/behavior_curriculum_settings.py +55 -0
- docket/models/behavior_curriculum_settings_preview_response.py +102 -0
- docket/models/behavior_curriculum_settings_preview_response_promoted.py +50 -0
- docket/models/behavior_curriculum_settings_version_response.py +160 -0
- docket/models/behavior_curriculum_settings_version_response_promoted.py +50 -0
- docket/models/caller_kind.py +11 -0
- docket/models/create_api_token_request.py +97 -0
- docket/models/create_application_principal_request.py +65 -0
- docket/models/create_personal_api_token_request.py +167 -0
- docket/models/create_recurring_acquisition_request.py +269 -0
- docket/models/create_role_assignment_request.py +99 -0
- docket/models/create_scheduled_acquisition_request.py +265 -0
- docket/models/create_template_request.py +323 -0
- docket/models/created_api_token_response.py +82 -0
- docket/models/effective_status.py +15 -0
- docket/models/fip_color.py +13 -0
- docket/models/fip_fiber.py +93 -0
- docket/models/fip_mode.py +9 -0
- docket/models/fip_push_settings_request.py +82 -0
- docket/models/fip_settings.py +91 -0
- docket/models/fip_settings_preview_response.py +97 -0
- docket/models/fip_settings_preview_response_promoted.py +50 -0
- docket/models/fip_settings_version_response.py +155 -0
- docket/models/fip_settings_version_response_promoted.py +50 -0
- docket/models/future_occurrences.py +9 -0
- docket/models/get_resolved_settings_response_200.py +65 -0
- docket/models/held_role_response.py +116 -0
- docket/models/http_validation_problem_details.py +204 -0
- docket/models/http_validation_problem_details_errors.py +59 -0
- docket/models/instrument_response.py +91 -0
- docket/models/kept_occurrence.py +85 -0
- docket/models/kept_occurrence_reason.py +10 -0
- docket/models/list_active_acquisitions_effective_status_item.py +15 -0
- docket/models/list_scheduled_acquisitions_planning_status.py +11 -0
- docket/models/me_caller.py +118 -0
- docket/models/me_permission.py +98 -0
- docket/models/me_response.py +158 -0
- docket/models/me_role.py +75 -0
- docket/models/occurrence_preview.py +121 -0
- docket/models/occurrence_summary.py +126 -0
- docket/models/occurrence_warnings.py +91 -0
- docket/models/permission_action.py +12 -0
- docket/models/person_assignment.py +74 -0
- docket/models/person_lookup_response.py +131 -0
- docket/models/person_response.py +103 -0
- docket/models/person_role.py +9 -0
- docket/models/personal_token_limits.py +92 -0
- docket/models/planning_status.py +11 -0
- docket/models/principal_kind.py +9 -0
- docket/models/principal_response.py +216 -0
- docket/models/problem_details.py +177 -0
- docket/models/project_response.py +75 -0
- docket/models/recurrence_exception_kind.py +13 -0
- docket/models/recurrence_exception_response.py +103 -0
- docket/models/recurrence_frequency.py +9 -0
- docket/models/recurrence_preview_response.py +101 -0
- docket/models/recurrence_rule.py +207 -0
- docket/models/recurring_acquisition_created_response.py +123 -0
- docket/models/recurring_acquisition_detail_response.py +105 -0
- docket/models/recurring_acquisition_response.py +326 -0
- docket/models/report_run_outcome_request.py +89 -0
- docket/models/resolution_mode.py +10 -0
- docket/models/resolved_settings.py +73 -0
- docket/models/role_assignment_response.py +157 -0
- docket/models/role_origin.py +10 -0
- docket/models/role_source_kind.py +10 -0
- docket/models/run_origin.py +9 -0
- docket/models/run_outcome.py +9 -0
- docket/models/run_response.py +181 -0
- docket/models/run_settings_snapshot.py +72 -0
- docket/models/run_start_response.py +91 -0
- docket/models/run_start_response_settings.py +65 -0
- docket/models/run_status.py +11 -0
- docket/models/scheduled_acquisition_edit_preview_response.py +150 -0
- docket/models/scheduled_acquisition_response.py +366 -0
- docket/models/set_planning_status_request.py +90 -0
- docket/models/settings_selector.py +138 -0
- docket/models/skipped_occurrence.py +85 -0
- docket/models/split_preview_response.py +129 -0
- docket/models/split_recurring_acquisition_request.py +261 -0
- docket/models/split_recurring_acquisition_response.py +159 -0
- docket/models/start_run_request.py +73 -0
- docket/models/subject_lookup_response.py +149 -0
- docket/models/subject_response.py +128 -0
- docket/models/sync_counts.py +80 -0
- docket/models/sync_response.py +81 -0
- docket/models/template_response.py +302 -0
- docket/models/update_scheduled_acquisition_request.py +235 -0
- docket/models/update_template_request.py +279 -0
- docket/models/weekday.py +14 -0
- docket/types.py +54 -0
- docket_auth/__init__.py +239 -0
- docket_client-0.3.2.dist-info/METADATA +233 -0
- docket_client-0.3.2.dist-info/RECORD +188 -0
- docket_client-0.3.2.dist-info/WHEEL +4 -0
- docket_client-0.3.2.dist-info/entry_points.txt +2 -0
- docket_mcp/__init__.py +6 -0
- docket_mcp/api.py +207 -0
- docket_mcp/config.py +57 -0
- docket_mcp/server.py +78 -0
- docket_mcp/signin.py +155 -0
- docket_mcp/slots.py +80 -0
- docket_mcp/times.py +160 -0
- docket_mcp/tools.py +1202 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: docket-client
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: Typed Python client for the docket scheduling API
|
|
5
|
+
Project-URL: Homepage, https://github.com/AllenNeuralDynamics/docket
|
|
6
|
+
Project-URL: Repository, https://github.com/AllenNeuralDynamics/docket
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/AllenNeuralDynamics/docket/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/AllenNeuralDynamics/docket/releases
|
|
9
|
+
Author: Allen Institute for Neural Dynamics
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: attrs>=22.2.0
|
|
22
|
+
Requires-Dist: httpx<0.29.0,>=0.23.1
|
|
23
|
+
Provides-Extra: auth
|
|
24
|
+
Requires-Dist: msal<2,>=1.31; extra == 'auth'
|
|
25
|
+
Provides-Extra: mcp
|
|
26
|
+
Requires-Dist: mcp<2,>=1.30; extra == 'mcp'
|
|
27
|
+
Requires-Dist: msal<2,>=1.31; extra == 'mcp'
|
|
28
|
+
Requires-Dist: tzdata>=2024.1; (sys_platform == 'win32') and extra == 'mcp'
|
|
29
|
+
Requires-Dist: tzlocal<6,>=5; extra == 'mcp'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# docket
|
|
33
|
+
|
|
34
|
+
A typed Python client for the **docket** scheduling API.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install docket-client
|
|
40
|
+
# the unreleased version, from the repository:
|
|
41
|
+
pip install "docket-client @ git+https://github.com/AllenNeuralDynamics/docket#subdirectory=client"
|
|
42
|
+
# or, from the repo root during development:
|
|
43
|
+
pip install ./client
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from docket import Client
|
|
50
|
+
|
|
51
|
+
client = Client(base_url="http://localhost:8001")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For authenticated endpoints use `AuthenticatedClient`:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from docket import AuthenticatedClient
|
|
58
|
+
|
|
59
|
+
client = AuthenticatedClient(base_url="https://api.example.com", token="SuperSecretToken")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Call any endpoint:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from docket.api.scheduled_acquisitions import list_active_acquisitions
|
|
66
|
+
|
|
67
|
+
sessions = list_active_acquisitions.sync(
|
|
68
|
+
client=client,
|
|
69
|
+
instrument_id="BEH.B-Box1",
|
|
70
|
+
before_minutes=15,
|
|
71
|
+
effective_status=["scheduled", "ongoing"],
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Every endpoint module exposes four variants:
|
|
76
|
+
|
|
77
|
+
| Function | Behaviour |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `sync` | Blocking — returns the parsed response body or `None` |
|
|
80
|
+
| `sync_detailed` | Blocking — always returns a `Response` object with status code, headers, and `parsed` |
|
|
81
|
+
| `asyncio` | Async equivalent of `sync` |
|
|
82
|
+
| `asyncio_detailed` | Async equivalent of `sync_detailed` |
|
|
83
|
+
|
|
84
|
+
See [examples/downstream_usage.py](examples/downstream_usage.py) for a
|
|
85
|
+
full end-to-end walkthrough.
|
|
86
|
+
|
|
87
|
+
## MCP server (`docket-mcp`)
|
|
88
|
+
|
|
89
|
+
`docket-mcp` lets an AI assistant (Claude Code, Claude Desktop, any MCP client) look at the
|
|
90
|
+
schedule and change it, through the same API as everyone else. It runs **locally over stdio**:
|
|
91
|
+
the client starts it on your machine, so it reaches Docket over your own network or VPN and
|
|
92
|
+
nothing new is hosted. It is the `docket_mcp` package here, next to `docket_auth`.
|
|
93
|
+
|
|
94
|
+
### Install
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pip install "docket-client[mcp]"
|
|
98
|
+
# or as an isolated tool (puts `docket-mcp` on your PATH):
|
|
99
|
+
uv tool install "docket-client[mcp]"
|
|
100
|
+
# from a checkout:
|
|
101
|
+
uv tool install "./client[mcp]"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Add it to your assistant
|
|
105
|
+
|
|
106
|
+
Claude Code:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
claude mcp add docket -e DOCKET_API_URL=http://localhost:8001 -e DOCKET_TIME_ZONE=America/Los_Angeles -- docket-mcp
|
|
110
|
+
# with an API token (see "Sign-in"):
|
|
111
|
+
claude mcp add docket -e DOCKET_API_URL=https://docket.example.org -e DOCKET_API_TOKEN=dkt_... -- docket-mcp
|
|
112
|
+
# straight from a checkout, without installing:
|
|
113
|
+
claude mcp add docket -e DOCKET_API_URL=http://localhost:8001 -- uv run --project ./client --extra mcp docket-mcp
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Claude Desktop (`claude_desktop_config.json`):
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"mcpServers": {
|
|
121
|
+
"docket": {
|
|
122
|
+
"command": "docket-mcp",
|
|
123
|
+
"env": {
|
|
124
|
+
"DOCKET_API_URL": "http://localhost:8001",
|
|
125
|
+
"DOCKET_TIME_ZONE": "America/Los_Angeles",
|
|
126
|
+
"DOCKET_API_TOKEN": "dkt_..."
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
If `docket-mcp` is not on the app's `PATH`, give its full path as `command`.
|
|
134
|
+
|
|
135
|
+
### Configuration
|
|
136
|
+
|
|
137
|
+
All through environment variables:
|
|
138
|
+
|
|
139
|
+
| Variable | Meaning |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| `DOCKET_API_URL` | The API's address. Default `http://localhost:8001`. A frontend origin works too (`https://docket.example.org` or `…/api`), with its URL prefix if it has one (`https://host/docket`). |
|
|
142
|
+
| `DOCKET_TIME_ZONE` | IANA zone for the local times you give and get back. Default: this machine's zone. A tool's `time_zone` argument overrides it. |
|
|
143
|
+
| `DOCKET_API_TOKEN` | A Docket API token (`dkt_…`). Checked **first**; works in Tokens and Entra mode. |
|
|
144
|
+
| `DOCKET_AUTH_CLIENT_ID`, `DOCKET_AUTH_SCOPE`, `DOCKET_AUTH_TENANT_ID` (or `DOCKET_AUTH_AUTHORITY`) | Entra sign-in when there is no API token: the SPA's public client id, `api://…/access_as_user`, and the tenant (or another OIDC authority, such as the mock provider). |
|
|
145
|
+
| `DOCKET_AUTH_CLIENT_SECRET` | With the three above: sign in as an application (client credentials) instead of as a person. |
|
|
146
|
+
| `DOCKET_AUTH_CACHE_PATH` | Where the person's token cache lives. Default `~/.docket/mcp-token-cache.json` (holds refresh tokens: protect it). |
|
|
147
|
+
|
|
148
|
+
### Sign-in
|
|
149
|
+
|
|
150
|
+
Reads are anonymous. The server asks `/api/v1/me` once for `auth_mode`, and only **writes** carry a token.
|
|
151
|
+
|
|
152
|
+
- `auth_mode: disabled` (the default Docket): nothing to set up and you are never asked to sign in.
|
|
153
|
+
- `auth_mode: tokens`: set `DOCKET_API_TOKEN`. Ask an admin for a token from **Settings > Access**. Without one, write tools say so.
|
|
154
|
+
- `auth_mode: entra`: set `DOCKET_API_TOKEN` too (simplest), or sign in as yourself. The first write prints a URL and a code to stderr **and** returns them as the tool's error text (some clients hide stderr); open the URL, enter the code, then run the tool again. The token is cached afterwards. Set `DOCKET_AUTH_CLIENT_SECRET` for an unattended application.
|
|
155
|
+
|
|
156
|
+
Tokens are never logged or returned by a tool. `whoami` shows your auth mode, caller, roles (and where each came from) and grants: as yourself when a credential is available without interaction (an API token, client credentials or a cached sign-in), otherwise as anonymous (`signed_in: false`). It never starts a sign-in.
|
|
157
|
+
|
|
158
|
+
The device-code token cache holds refresh tokens. It is created owner-only (`chmod 600`) on Linux and macOS; on Windows `chmod` is a no-op and the file relies on your user profile's ACL, so keep it there (the default is `~/.docket/`).
|
|
159
|
+
|
|
160
|
+
### Tools
|
|
161
|
+
|
|
162
|
+
| Read | |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| `list_rigs`, `list_projects` | what you can book, with optional `search` |
|
|
165
|
+
| `lookup_subject`, `lookup_person` | check an id or username exists |
|
|
166
|
+
| `get_schedule` | sessions between two dates (rig, subject, person, archived filters) |
|
|
167
|
+
| `get_acquisition` | one session in full: people, notes, resolved settings, runs |
|
|
168
|
+
| `find_free_slots` | gaps on a rig long enough for a session, within daily bounds |
|
|
169
|
+
| `list_series`, `get_series` | recurring series and their rules |
|
|
170
|
+
| `list_templates`, `list_settings`, `list_runs`, `whoami` | |
|
|
171
|
+
|
|
172
|
+
| Write (all take `dry_run`, default **true**) | |
|
|
173
|
+
| --- | --- |
|
|
174
|
+
| `schedule_acquisition` | book one session (optionally from a template) |
|
|
175
|
+
| `move_acquisition` | change start, length or rig |
|
|
176
|
+
| `archive_acquisition` | archive a session (nothing is ever hard-deleted) |
|
|
177
|
+
| `create_series` | a finite daily/weekly series |
|
|
178
|
+
| `edit_series_from` | change a series from a date on (the old one ends, a new one starts) |
|
|
179
|
+
| `archive_series` | archive a series, keeping or archiving its upcoming sessions |
|
|
180
|
+
| `mark_outcome` | record succeeded / failed / cancelled by hand |
|
|
181
|
+
|
|
182
|
+
Not in v1: pushing settings, syncing vocabulary, managing templates and roles.
|
|
183
|
+
|
|
184
|
+
### Dry runs
|
|
185
|
+
|
|
186
|
+
Every write tool previews by default and changes nothing: it returns what would happen,
|
|
187
|
+
including overlap warnings, skipped (daylight-saving) dates and which sessions a series edit
|
|
188
|
+
would replace or keep. The tool descriptions tell the assistant to show you the preview and
|
|
189
|
+
repeat the call with `dry_run=false` only once you agree. Series and moves use the server's own
|
|
190
|
+
preview endpoints; for a single session the API has none, so the preview checks the schedule
|
|
191
|
+
itself with the server's overlap rule (same rig only; the same subject on another rig is listed
|
|
192
|
+
as `advisory`) and the server checks again when it creates it.
|
|
193
|
+
|
|
194
|
+
Each preview returns what the confirmation needs, under `confirm_with`: the `expected_version`
|
|
195
|
+
of what it looked at (moves, archives, outcomes, series edits and archives) and an
|
|
196
|
+
`idempotency_key` (new sessions, series and series edits). Repeat the call with
|
|
197
|
+
`dry_run=false` and those values. If the session or series changed in between, the call is
|
|
198
|
+
refused with "changed since your preview; preview again" and nothing is written. A retry
|
|
199
|
+
after a timeout with the same key cannot book or create twice.
|
|
200
|
+
|
|
201
|
+
### Times
|
|
202
|
+
|
|
203
|
+
Give local wall-clock times such as `2026-03-08T09:30`, never offsets. Results show the local
|
|
204
|
+
time (with its offset) and UTC. A time that does not exist (a spring-forward gap) is refused;
|
|
205
|
+
a repeated one (fall back) takes the first occurrence and says so.
|
|
206
|
+
|
|
207
|
+
### Example prompts
|
|
208
|
+
|
|
209
|
+
- "What is on rig `BEH.A` next week, and which afternoons are free for a 90-minute session?"
|
|
210
|
+
- "Book subject 812345 on `BEH.A` under project `Vision` tomorrow at 10:00 for an hour, with me as experimenter." (it previews first)
|
|
211
|
+
- "Move session 412 to Thursday at 14:00."
|
|
212
|
+
- "Set up a weekly series, Mondays and Thursdays at 09:00 for eight sessions, for subject 812345."
|
|
213
|
+
- "From 2026-11-02, shift that series to 10:30." / "Cancel the rest of series 17 but keep what already ran."
|
|
214
|
+
- "Did anything fail on the FIP rigs this week?"
|
|
215
|
+
|
|
216
|
+
### Developing
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
uv sync --group dev
|
|
220
|
+
uv run pytest client/tests # unit tests + the stdio smoke test
|
|
221
|
+
uv run pytest client/tests -m integration # schedule -> move -> archive on a real server
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The integration test builds the server image and starts its own Compose project
|
|
225
|
+
(`docket-mcp-it`, API on port 8020, static vocabulary, nothing from the institute) and removes
|
|
226
|
+
it afterwards; set `DOCKET_IT_URL` to use a server that is already running instead.
|
|
227
|
+
|
|
228
|
+
## Building / publishing
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# from the repo root
|
|
232
|
+
uv build --package docket-client # → dist/docket_client-*.whl
|
|
233
|
+
```
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
docket/__init__.py,sha256=mXHDX36eM0PU_Xd8Z4QOu5uIMU_QOLVChHkMzeMXAH8,153
|
|
2
|
+
docket/client.py,sha256=RNOlUXJyXojGoTe70XQ7Ua3F6vL9VEVZrcyuN4a559I,12448
|
|
3
|
+
docket/errors.py,sha256=gO8GBmKqmSNgAg-E5oT-oOyxztvp7V_6XG7OUTT15q0,546
|
|
4
|
+
docket/types.py,sha256=Hs1kUAG3WouD0popBN5bf1TsZiJ73ARUAKs4UOJG_Lo,1343
|
|
5
|
+
docket/api/__init__.py,sha256=zTSiG_ujSjAqWPyc435YXaX9XTlpMjiJWBbV-f-YtdA,45
|
|
6
|
+
docket/api/access/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
7
|
+
docket/api/access/add_principal.py,sha256=0V2QI_d_xmSOoOhIu2FOwbw6fS8LTprrR7z8ja1r1J4,5956
|
|
8
|
+
docket/api/access/archive_role_assignment.py,sha256=BD39Ur9el4_JFGFrlNEMn3s9oB9UbKcFWFOAc7dYfB0,4936
|
|
9
|
+
docket/api/access/create_api_token.py,sha256=f-s8Pcnx5kJVtZVN4Eu-RqgjnWJAyopgrEUX4NjieI8,6254
|
|
10
|
+
docket/api/access/create_application_principal.py,sha256=6xxqWrOuzKphkDnRxYvMWzetUd8JKdpdhq0wYBRz9I8,6269
|
|
11
|
+
docket/api/access/create_role_assignment.py,sha256=JaZaejO2jMhVYA0ucmcZKmWnKgGvy3ih0Lwj7yAc6aQ,5867
|
|
12
|
+
docket/api/access/list_api_tokens.py,sha256=F0Eptt9fOnSS8irVVC7gZtO1tJNYlaiZM4RIUOkTWN8,5058
|
|
13
|
+
docket/api/access/list_principals.py,sha256=F2oLioAk7-VTIqs8NKurRrSqGXw2GWhMhZo1yEJSPN0,5061
|
|
14
|
+
docket/api/access/list_role_assignments.py,sha256=kPrAR5C6T5ebxYZ-xaCEcV1djpkFxKV9tUmQ_45nFxY,5820
|
|
15
|
+
docket/api/access/revoke_api_token.py,sha256=SIHQfjJLPp-n9x3T6VKyOKT8I3OmZlImiKO3dGab79o,4421
|
|
16
|
+
docket/api/identity/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
17
|
+
docket/api/identity/create_my_api_token.py,sha256=dWdLfR-nEYRn1xxtEY8fUwbt5Ii1O2-xBtpmXuYNgU8,6847
|
|
18
|
+
docket/api/identity/get_me.py,sha256=qItA6ZkXxfILbvko32T58WqdRhWyRm5PN7IbRRu8aDs,3104
|
|
19
|
+
docket/api/identity/list_my_api_tokens.py,sha256=B8j8joE6grTSPAADxYxHvgxSsdE0Z-ZBOwkVfC6qaOc,4390
|
|
20
|
+
docket/api/identity/revoke_my_api_token.py,sha256=3s0G6EWu7jiNoGZbQQfr2roncH6kmuyfY0m09SqwT-4,4504
|
|
21
|
+
docket/api/recurring_acquisitions/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
22
|
+
docket/api/recurring_acquisitions/archive_recurring_acquisition.py,sha256=t8bbswk6iRhrqovmPw4cAZrER5ohmjYk83ZYXulQNhQ,6428
|
|
23
|
+
docket/api/recurring_acquisitions/create_recurring_acquisition.py,sha256=lKcQj1jBJ3oVPQLOMP5MVXk74awamfys4XlzY1_WTOM,7331
|
|
24
|
+
docket/api/recurring_acquisitions/get_recurring_acquisition.py,sha256=nMoV8muigj6nDCtVk-GiuUtz-aJKrCJog6w4_htny3w,4399
|
|
25
|
+
docket/api/recurring_acquisitions/list_recurring_acquisitions.py,sha256=wmUPvLjGSnYGH0HqSuRB8dmM4ygqHEWJZbYlk76iP3Y,7725
|
|
26
|
+
docket/api/recurring_acquisitions/preview_recurring_acquisition.py,sha256=sQlMa1vhbOs1A-UpoZrkseqoaoXOvRNq9otIiVPrKLE,6203
|
|
27
|
+
docket/api/recurring_acquisitions/preview_split_recurring_acquisition.py,sha256=GIOJVOxnE820Nl4p5VZDxbvKx70UKzWydnXA5992ahE,8139
|
|
28
|
+
docket/api/recurring_acquisitions/split_recurring_acquisition.py,sha256=CfBlD_gKhZ3LgcTEc5og5V7e-qUTFl0er7EY_AE1lcs,9495
|
|
29
|
+
docket/api/runs/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
30
|
+
docket/api/runs/get_run.py,sha256=53faiXYr8tilRQJFKoP5rywTaAWCt8eIqWdTfLpbwAM,3883
|
|
31
|
+
docket/api/runs/list_runs.py,sha256=nn5mSzYzaw0NFlH5Z2V7oEM4rgG8fmetrBchzLAPXDY,4816
|
|
32
|
+
docket/api/runs/report_run_outcome.py,sha256=sT5FbEfgty0XCBpryJ6rAiZQafn4laS7nZWYivDzE-A,5924
|
|
33
|
+
docket/api/scheduled_acquisitions/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
34
|
+
docket/api/scheduled_acquisitions/archive_scheduled_acquisition.py,sha256=QnXScKVPI-QyyjdcJfmSZK7P17pQRVYO0I1KgnySHkE,5980
|
|
35
|
+
docket/api/scheduled_acquisitions/create_scheduled_acquisition.py,sha256=Xx1l4UQ7pmTGBRrC8pyXz6dZcONyLoF-iBU14NBSb9s,5768
|
|
36
|
+
docket/api/scheduled_acquisitions/get_resolved_settings.py,sha256=m-8iO5kMagbBLvaIWbXnk8ASTd13n-1mfwOZFkTK7zE,4916
|
|
37
|
+
docket/api/scheduled_acquisitions/get_scheduled_acquisition.py,sha256=RT9Eh8rD9nVcbo1ryiMYgY8FBJG1LPs0E_Gl8QFrzas,4032
|
|
38
|
+
docket/api/scheduled_acquisitions/list_active_acquisitions.py,sha256=kZaqYp3qWTV5-wdQSbpwH96iRg43L20iTedx4FaOL90,10278
|
|
39
|
+
docket/api/scheduled_acquisitions/list_runs_for_scheduled_acquisition.py,sha256=tg737gKCQtylQoHom-cR3IOtG9FbiLfDi2kR1MHOqEU,4839
|
|
40
|
+
docket/api/scheduled_acquisitions/list_scheduled_acquisitions.py,sha256=lZjNxLV5x9VWoah-cv6U8KjhzwXoL3reYnbKDyJNDrk,11573
|
|
41
|
+
docket/api/scheduled_acquisitions/preview_scheduled_acquisition_edit.py,sha256=3f8eDve9jAR96Tn759RdShVn0r6_G5lhsmBOuUYZD_c,7461
|
|
42
|
+
docket/api/scheduled_acquisitions/set_planning_status.py,sha256=27ttxqH1zw7AR7mHuos182uhoLQkmThMOhBxGqtlDsQ,5662
|
|
43
|
+
docket/api/scheduled_acquisitions/start_run.py,sha256=RR56dJJaob-CXGbZTv5Sj_jxCUp-crRwP3o3wJQkrrU,6573
|
|
44
|
+
docket/api/scheduled_acquisitions/update_scheduled_acquisition.py,sha256=ZWwpi-nCZ6a0VRV1-lCHx9JpMElNGzbqBn6g6KDKxyI,7271
|
|
45
|
+
docket/api/settings/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
46
|
+
docket/api/settings/list_behavior_curriculum_discriminator_values.py,sha256=1oXG-R-s4AbXmmqs9SKeYta3kzdw-AMA4xmDBbmVuw0,5089
|
|
47
|
+
docket/api/settings/list_behavior_curriculum_settings.py,sha256=MWJJaIH09ID2qpfPm7onqCfAaiujdhCbbB9BDpfo_ks,5992
|
|
48
|
+
docket/api/settings/list_fip_discriminator_values.py,sha256=5ZGay9NnXHu_CmpkYFwXY3oeSfDO1jr7felcsYhrtSY,4993
|
|
49
|
+
docket/api/settings/list_fip_settings.py,sha256=1vZ2EDjLzmGklblCwcg0GLkKjZOa6jpbN8plpHMUSBg,5677
|
|
50
|
+
docket/api/settings/list_live_behavior_curriculum_settings.py,sha256=_NwGd0awOeQtaNOu9LeV0Qrw5EWf4_poHFwg227ms0Q,7435
|
|
51
|
+
docket/api/settings/list_live_fip_settings.py,sha256=A20CWkO8zh8dChyr8wTksKot2E8u9m_si2BUYJrhl54,7104
|
|
52
|
+
docket/api/settings/preview_behavior_curriculum_settings.py,sha256=22x4WsaYcRDF7vh346lrFi7Y88ko-mA2V80IiSaNLx8,6424
|
|
53
|
+
docket/api/settings/preview_fip_settings.py,sha256=zbUhqijlSXdc8yoZoeIvVDkd7HuWzrHFf4SXojtbMA8,5253
|
|
54
|
+
docket/api/settings/push_behavior_curriculum_settings.py,sha256=hBTaRR9o0d0QEj4fYUFjV4-oZgiOarEu79OJ-bGsjUc,6965
|
|
55
|
+
docket/api/settings/push_fip_settings.py,sha256=rgy9xQAchdkBp66akJcaPe7zO_bD7wQUM7rNOxdhiyY,6463
|
|
56
|
+
docket/api/templates/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
57
|
+
docket/api/templates/archive_template.py,sha256=U5MbBg3YFMQ2hSgx54oLAnYd7K7UG4A7jV8iYaqwbjs,5737
|
|
58
|
+
docket/api/templates/create_template.py,sha256=TTNOk1xqhYyT53v3C2KTBQIXBpdVP0lmTkyy9GyMnvw,6788
|
|
59
|
+
docket/api/templates/get_template.py,sha256=R8yirbGaixhv8XyEh65hkRARwffHMSqlKxvJKMN65HE,3862
|
|
60
|
+
docket/api/templates/list_templates.py,sha256=3ly9Sj04b1iDhDORQKcOXObvDKhSv4OXyYvjj_m_zqk,4618
|
|
61
|
+
docket/api/templates/update_template.py,sha256=2_oo3e8c6-1cfEkJ4Df-nOlUfauFkwKFBPr19tT3SI4,7908
|
|
62
|
+
docket/api/vocabulary/__init__.py,sha256=5vd9uJWAjRqa9xzxzYkLD1yoZ12Ld_bAaNB5WX4fbE8,56
|
|
63
|
+
docket/api/vocabulary/list_instruments.py,sha256=yfan5XkiWFlg86y6KjbGgTL5JHo67JryusCC3ZevN7c,4115
|
|
64
|
+
docket/api/vocabulary/list_persons.py,sha256=5UrgYqJQbjmk9GS-tIhxjlzzipyDL1PJIHcmaSANNko,3408
|
|
65
|
+
docket/api/vocabulary/list_projects.py,sha256=etbT6RJGCQXvB1HVBNWjDzHROkTI3ZjE8UGAQxPCKmU,3422
|
|
66
|
+
docket/api/vocabulary/list_subjects.py,sha256=eyC1SjKYcFrz5Fs4pM55Tldmu2lX9C0KicL9J8lP1MI,3422
|
|
67
|
+
docket/api/vocabulary/lookup_person.py,sha256=MrV6jb4JCR6HtdVUPCyI9LrfSthyQGQhVBE5cZfd0to,4290
|
|
68
|
+
docket/api/vocabulary/lookup_subject.py,sha256=dexBxL65qaJ8JjLa5KbD7B710ZrZGHYhMq72rwt3Njc,4342
|
|
69
|
+
docket/api/vocabulary/sync_vocabulary.py,sha256=-1QdTclGJyYnkZuTre83Y6G2jKAQCc-VtHVijW3Ms5w,3891
|
|
70
|
+
docket/models/__init__.py,sha256=ikA3t_CDFT88dYDA6xTyR5UOPe-ggO0pQE0Diu3w3Kk,9478
|
|
71
|
+
docket/models/access_role.py,sha256=qWKVQ2cb3GbZFqLHv8LY9aRHp6SMZzGNmIni_G4JV0E,297
|
|
72
|
+
docket/models/access_role_nullable_type_1.py,sha256=aT8uRQANzPRRVyuwKKioVMWCY_quqkdtb3a9OiSDSfA,310
|
|
73
|
+
docket/models/access_role_nullable_type_2_type_1.py,sha256=KitsG4ftAYt-Zlo2HenR4qBryd8oVARrEer75JNN8bI,315
|
|
74
|
+
docket/models/access_role_nullable_type_3_type_1.py,sha256=T-ygFj-sizAjJaEUYIaGqWZJ1-61_oO7vnCwAJ8YMNo,315
|
|
75
|
+
docket/models/acquisition_variable.py,sha256=98P7L5RmvVC8ZFTJ1hvVNpW-2eKpbHIfzPlozHbjHvY,1783
|
|
76
|
+
docket/models/acquisition_warning.py,sha256=7GStpBEyFvcwMTyP-nmVxvxjIMvB4jbMyGOl62ltIkU,3931
|
|
77
|
+
docket/models/add_principal_request.py,sha256=jqHX5h3ZwycVSNx__D1lmHjRxFz3cLCqFQX1JR6I5lQ,2340
|
|
78
|
+
docket/models/api_token_response.py,sha256=u_cYyJ6OFRMr5vP66dCL4yXRXk98qF1pcAPRmzLUekA,9542
|
|
79
|
+
docket/models/api_token_status.py,sha256=3TPFDd6-dx4trrj_MXT0wndji5ZSBb6NLxCZHqErzVc,189
|
|
80
|
+
docket/models/archive_recurring_acquisition_request.py,sha256=du7-4MOkHjL29fHUNwIsKLJVIOcB3UC191mWG5zEYJQ,2455
|
|
81
|
+
docket/models/archive_recurring_acquisition_response.py,sha256=WyJqHwxM29sQq2Etp7ShKJQJ6ZOGSMgYExXTS7mtGRM,2702
|
|
82
|
+
docket/models/archive_scheduled_acquisition_request.py,sha256=8M86ZgewdA7yw8nIkzjDc4Q07XEx4mhyNTPZeFtDV8c,2497
|
|
83
|
+
docket/models/archive_template_request.py,sha256=ffEGatgrHqHVfQDDy4Bp68avubZ0k-0-vVu7xaTzOwc,1934
|
|
84
|
+
docket/models/auth_mode.py,sha256=aZgoSip5S0Ahnxt7sph2Yb3VPrF4EWfEGr0VdSin2NA,181
|
|
85
|
+
docket/models/behavior_curriculum_push_settings_request.py,sha256=vLp3nUKR7r7diBz_NGJm-fFZncKduZpsrTEpuJDLYMo,2698
|
|
86
|
+
docket/models/behavior_curriculum_settings.py,sha256=Muq0ge13FhD1O7hlApyQB2p3GGmYF_PU1JU8FLRqONA,1633
|
|
87
|
+
docket/models/behavior_curriculum_settings_preview_response.py,sha256=26l3Ir4IhTKBawoFmUEJBuCZ0DRA9yIS2A-tem2ih94,3552
|
|
88
|
+
docket/models/behavior_curriculum_settings_preview_response_promoted.py,sha256=gkhtsWxC_XWowP0z-z6uO6zMsQGQ5Y00u_74hkq3whc,1557
|
|
89
|
+
docket/models/behavior_curriculum_settings_version_response.py,sha256=3acFoeKKPUZj7tQvg8R25lUCtHlNowEBSVU4kUXUq4g,5603
|
|
90
|
+
docket/models/behavior_curriculum_settings_version_response_promoted.py,sha256=nd0IASNNwsUD-MZrezzK_NIeKEAJd2rjHQIGn2ui7r8,1557
|
|
91
|
+
docket/models/caller_kind.py,sha256=QJI--8_BxjmzKINVOuKr0Ev-XfpmuUpL0oplw_Poo7s,217
|
|
92
|
+
docket/models/create_api_token_request.py,sha256=OMYnr0mssMh2QTbnvLsul3C5f9Yyqy2ka-qeziG3E1I,2912
|
|
93
|
+
docket/models/create_application_principal_request.py,sha256=6iANl-qR7G3z9oq9-073CRkUv4oXBx2ouy9i8SGWvEg,1870
|
|
94
|
+
docket/models/create_personal_api_token_request.py,sha256=FLUyDfkXMFWnp6ipTeHyUfoBtQmegKHELSbZ0X8angc,5699
|
|
95
|
+
docket/models/create_recurring_acquisition_request.py,sha256=o7TMCMJLT-l5IarfJU4ivLCgRgK0sFLNIA9SxBX_k4I,10867
|
|
96
|
+
docket/models/create_role_assignment_request.py,sha256=DY2tfrE-aqKXTgRZCBoHUYVi8Jwuv42aOXf1C4WPPjo,3042
|
|
97
|
+
docket/models/create_scheduled_acquisition_request.py,sha256=M-0gaUxEaS8sj3jVCIEQDSY-zUoF3OvMw0gwe63u-a4,9976
|
|
98
|
+
docket/models/create_template_request.py,sha256=BP7APCY9I-7LeBU3UxVWStcnqqjMNfXq5qcFrzdEOi4,12311
|
|
99
|
+
docket/models/created_api_token_response.py,sha256=qhP14Mq_jjTrW1RXObQtHWsisF8ewicPRruHl4d8p9Q,2363
|
|
100
|
+
docket/models/effective_status.py,sha256=LCY7nDShbxcLx8tFXTEuNKnCWx9I1mfBQYVN1URV0EM,330
|
|
101
|
+
docket/models/fip_color.py,sha256=v5TziP1WWvSzKk_2LCWnpajJgcf358lTwQgLqnacKqo,233
|
|
102
|
+
docket/models/fip_fiber.py,sha256=bdOUByJdJXVq6wQnvD2c86vjZZE0UWXHFSkRMILmqGs,2662
|
|
103
|
+
docket/models/fip_mode.py,sha256=b2u56KhLkIQDwD4DYMLZ1UtGEsvKvzlj-NCXBy_iqTQ,158
|
|
104
|
+
docket/models/fip_push_settings_request.py,sha256=Q-3Ii8IOquRvhKnw80hkWBSMzP-bOjCoi2YulDMdNbk,2302
|
|
105
|
+
docket/models/fip_settings.py,sha256=KF6xTkgRrC_r25AdofwczmXgfD51iIggFTMpULbki90,2413
|
|
106
|
+
docket/models/fip_settings_preview_response.py,sha256=UnxT6Cad4kDRkXXbcoZuGkFBk6XIuHz80gSpSpWLhr8,3001
|
|
107
|
+
docket/models/fip_settings_preview_response_promoted.py,sha256=Mc3fNZmF3d1Ogzs-2LsJ0q9P1v5kGn_RYDSbA4xiMEc,1479
|
|
108
|
+
docket/models/fip_settings_version_response.py,sha256=gIsDkTmVj25yM6HtfiR6dO5b1TMj9KuuWRFVgGwP9Ok,5052
|
|
109
|
+
docket/models/fip_settings_version_response_promoted.py,sha256=V-HtGVdxV75wkmNa0O9NzT90_OufZh-pPT3A_z7s9BE,1479
|
|
110
|
+
docket/models/future_occurrences.py,sha256=p4Y_QH32uvZ9V_0ttao6PsCb0IBFZOyr-0bnvyy-pSs,164
|
|
111
|
+
docket/models/get_resolved_settings_response_200.py,sha256=HyRstSONqyLneSdRdTKl0vBkUvhm9RdHbr_atlH_MhA,1999
|
|
112
|
+
docket/models/held_role_response.py,sha256=uCYqsiFfdXovuvznLSXMUkhfnGLgbqtza3gttoi8D_g,3408
|
|
113
|
+
docket/models/http_validation_problem_details.py,sha256=g4rZwOvp8pa-jT5ebo6_XHbI_Z7b2MLHyVDFlw_eOwg,6260
|
|
114
|
+
docket/models/http_validation_problem_details_errors.py,sha256=gqdYs_dHGp2V4RTkmfrXaqHYXJWkDDaL7GfjH-8LOl4,1769
|
|
115
|
+
docket/models/instrument_response.py,sha256=qLRqPvnDbZNSsuC-NnHEhWJv_qAa9S_P5BQK-kx1clQ,2282
|
|
116
|
+
docket/models/kept_occurrence.py,sha256=1z1J_A9zsdsJ66nRtNrni_jwXWztXpd99akl_e-C6q4,2669
|
|
117
|
+
docket/models/kept_occurrence_reason.py,sha256=DcIT5qbnanx0mKdzH5WHNCg0M4Gq3qN1W6SIoxIJnvQ,195
|
|
118
|
+
docket/models/list_active_acquisitions_effective_status_item.py,sha256=NLWnHJHcXjc0WqmOrD4FZPxGaIlkQDbuQIeIzzfQW28,356
|
|
119
|
+
docket/models/list_scheduled_acquisitions_planning_status.py,sha256=Rk1gkFHn3fYJnXjY1OmgnJXifQod_pciDjQOFMiBF68,250
|
|
120
|
+
docket/models/me_caller.py,sha256=CqzynuWxH7ki4hYVK9eZRddMsIxo39bK5x3063dmh5E,3509
|
|
121
|
+
docket/models/me_permission.py,sha256=r2jCTdEY34BS_iEO8x72Y0a3ZaRdK4ZsDZ1fpuF7Vs8,2701
|
|
122
|
+
docket/models/me_response.py,sha256=DTWgekrV0e4-CnKhN2Ar8a9akQoElj4rJ8ICOK-GNAA,5449
|
|
123
|
+
docket/models/me_role.py,sha256=UFfRzzVjFP4jkFrGzj5gZSq8xrF-ppdXjyUOl7RJDvA,1924
|
|
124
|
+
docket/models/occurrence_preview.py,sha256=VQn-aAx_mvmnGR-MEm5bGetBFXe0qPxNuWicIYLf37o,4175
|
|
125
|
+
docket/models/occurrence_summary.py,sha256=cuA0i3EiMnD5Is-uspzGWJ8Y2e_yZPSQQxk8qeUayfY,4203
|
|
126
|
+
docket/models/occurrence_warnings.py,sha256=C02Rf66Q6riToYlEJuNaqpMUhPewGybOwudHcbuzkTk,2715
|
|
127
|
+
docket/models/permission_action.py,sha256=aJliPOYJpeVhjQ_10AwnLUw34KMhxIdWnmphQFUGm3A,231
|
|
128
|
+
docket/models/person_assignment.py,sha256=_ME39Pb4YiolHyhursEqY01BYjKRPGcYLccXyY6mSAc,1889
|
|
129
|
+
docket/models/person_lookup_response.py,sha256=YDTbbL2sTI8LCc-TGb_g_3RC2D14fjx0oo71BvvLHoY,3779
|
|
130
|
+
docket/models/person_response.py,sha256=s15QvZD3wiJ8Qpf1X5cU4pF7108qvOxLrE-oK_-m-Co,2720
|
|
131
|
+
docket/models/person_role.py,sha256=NEWl-WLFLd7-BmQ9XBYi12Ng_zEDZXslYv2z4moUZ00,195
|
|
132
|
+
docket/models/personal_token_limits.py,sha256=HxNh_CInW5HHR8tJ6CekYBzwPnnmxckLrltYhhIVa0k,3012
|
|
133
|
+
docket/models/planning_status.py,sha256=K7dMJZYMcermMfWv8EljW_HUt4_j6eAWWaT1zQBIA4w,225
|
|
134
|
+
docket/models/principal_kind.py,sha256=x9X2HbMcpso8N3eW-BF2TXhT7bgVj3ynAAwPhPABCBs,172
|
|
135
|
+
docket/models/principal_response.py,sha256=MprsDZxFltiC8-4WTsxjdBtxD77ifUfuNTJ6cZ8yk_M,7789
|
|
136
|
+
docket/models/problem_details.py,sha256=anILAvUU2JQl-0CxqRH1TN_uAXO-FUC-WRFArJuNi9Y,5189
|
|
137
|
+
docket/models/project_response.py,sha256=0U-MWzYYOkm9Eu6q46949IW3rWKKdancep9d55pfOP8,1921
|
|
138
|
+
docket/models/recurrence_exception_kind.py,sha256=-TG6BR8dFtsR1juQlCeKCNnXM3wZENXIJaCZsJ1jh9U,362
|
|
139
|
+
docket/models/recurrence_exception_response.py,sha256=jkhSTHjOM9-zgdfV5bwvvlcMWWPfhjnGMxBpt5zMTmM,3456
|
|
140
|
+
docket/models/recurrence_frequency.py,sha256=EJd18ujrVqlb3IsHFQr9Q961yj1frw0dZcOpV-Tzp5E,166
|
|
141
|
+
docket/models/recurrence_preview_response.py,sha256=Jm-2oAsmMB8_8Lzto_3HCX-_VvRSMcReVbiH2ol34rI,3204
|
|
142
|
+
docket/models/recurrence_rule.py,sha256=yAJCN9w7ecdshyoZ_G2M3ksj5xLvs5ZO6qSiejOiJ1s,7148
|
|
143
|
+
docket/models/recurring_acquisition_created_response.py,sha256=ZnduO3MXjs3ZO_52fPLb2XpizD6RgUNgYR6f1k_n6-I,4309
|
|
144
|
+
docket/models/recurring_acquisition_detail_response.py,sha256=NuN4q--s_5LbVQQdir79TdL4DYxTvD3WVDDpQJN77Q0,3701
|
|
145
|
+
docket/models/recurring_acquisition_response.py,sha256=_ELOHRgOtvb0N5ke4yPGiX9MvuC64F-WrBM9DfDVdyU,11562
|
|
146
|
+
docket/models/report_run_outcome_request.py,sha256=4Ge2y05BbiI4Os_9rrWHlszBt24zvt9GqhzapH4DecE,2569
|
|
147
|
+
docket/models/resolution_mode.py,sha256=UnI3saqdfq65xsxxT6ytVG6vGLYpRPuaYTXTFA9AbcA,209
|
|
148
|
+
docket/models/resolved_settings.py,sha256=YHfHzcMfVKQ0Nn_40iCSGyJlYYDTPfa-A24RYJiBKco,1875
|
|
149
|
+
docket/models/role_assignment_response.py,sha256=GSruhGF7gkX--pdXUGInlXF36x0X7SK80PwujdSe4bY,5147
|
|
150
|
+
docket/models/role_origin.py,sha256=VCtrRpLcewjY1D1Gt-iUjBaAFz_bVPCkF58sWbvOSgs,197
|
|
151
|
+
docket/models/role_source_kind.py,sha256=9Bh6S1oIkOJj7kUXef0kBTBFI3YmKk3jvVioidHanSM,201
|
|
152
|
+
docket/models/run_origin.py,sha256=ABR3EFipFwRyHHcZ2ZXzmUvpsPKQ9J3AKEXrh5tJk2E,156
|
|
153
|
+
docket/models/run_outcome.py,sha256=S8Sxa4vCtTulOJuNeZNE6vyBW-Qz3W3fWr1wEky8HWI,167
|
|
154
|
+
docket/models/run_response.py,sha256=69X3Rh6Oy6GOBxDFoGQ9Irvg2MBlhzBTOZGVndxYpfY,5595
|
|
155
|
+
docket/models/run_settings_snapshot.py,sha256=v1PBssFrIXv2gXLXjr_0jmkn1N3dcA6GxBkjjYQ-9T8,1985
|
|
156
|
+
docket/models/run_start_response.py,sha256=BIe3EuTozc589o4z7uikV_yNljeBARHvVhpTcpMv2k4,2520
|
|
157
|
+
docket/models/run_start_response_settings.py,sha256=rYYsMBeQDO85xBvAlW9qw9-FwIm5kq4lP1-lYN02MZE,2028
|
|
158
|
+
docket/models/run_status.py,sha256=idt5-R9CD0jZg0ho-VyE0z_6grXjBbno9ZSR4PjPGRE,218
|
|
159
|
+
docket/models/scheduled_acquisition_edit_preview_response.py,sha256=srSR1v4tIyzOZQUgruh80sGrq1tB2tVvTkB8F-QipVk,5323
|
|
160
|
+
docket/models/scheduled_acquisition_response.py,sha256=u4gCN_0BA-FGHEbzbKRG-Aq6hoARAHKQwE1AJaFDPTk,13568
|
|
161
|
+
docket/models/set_planning_status_request.py,sha256=vj30lagVdhJVs_pupaG36QfVHKQwHpEh-Kb4G4otomM,2963
|
|
162
|
+
docket/models/settings_selector.py,sha256=bqX5Vxra5RUfGQXU-8dpHKBnb_caaYeIfknwu5x6aQ0,4900
|
|
163
|
+
docket/models/skipped_occurrence.py,sha256=RgP4qGkytpONsppYkQYGRleAuvJ9-BOK-NwnLKBmUfk,2538
|
|
164
|
+
docket/models/split_preview_response.py,sha256=YSyvPbD_kI1xZSas3o5OBY0r7mTZ_VgBRL8eaRQpk7o,4229
|
|
165
|
+
docket/models/split_recurring_acquisition_request.py,sha256=4jS_pfXJCeui7nXg7KLHpTMTf6VoSrESu14zmr6c-ho,10045
|
|
166
|
+
docket/models/split_recurring_acquisition_response.py,sha256=1FzZmqRQSC5-PkDZPBdRY_0zqLMLQpC2PSTxGcyCtqg,5640
|
|
167
|
+
docket/models/start_run_request.py,sha256=3lwwvlENoZp0o-U4QwEA2JfPTUxn5o0qwm03HisS1sE,2078
|
|
168
|
+
docket/models/subject_lookup_response.py,sha256=FMVOdZMIaCo55YpsWHpbb5c7UyUo8ObvTClyD8pN-QA,4494
|
|
169
|
+
docket/models/subject_response.py,sha256=iExW1E7fgMzigEinUFoaSvIxRiQCNXEbRfCHhRJsvKk,3684
|
|
170
|
+
docket/models/sync_counts.py,sha256=MYD5dQyyTeBc0Dwv-99pM3My_aDVqGF1NfIj8c22Au0,1943
|
|
171
|
+
docket/models/sync_response.py,sha256=Ocn-RWEIJwfnFiPnE2MN-q0YmHKAN3BSfAJtxy38w04,2100
|
|
172
|
+
docket/models/template_response.py,sha256=9rxRqK44AXY9NeSTDy8EGw8djJL0jWmhrGK5Un-WuFw,9966
|
|
173
|
+
docket/models/update_scheduled_acquisition_request.py,sha256=IUgkJOeP5nKDsOcfQj7ZW5wQyJMeOf-g4m9T-OSIpcI,9333
|
|
174
|
+
docket/models/update_template_request.py,sha256=kgUYLx_rUVss9OyAgcKenehi-PRIcQ_a7ZeygraFr-s,10653
|
|
175
|
+
docket/models/weekday.py,sha256=ynDKgQZbEeeJU3f6AAzURIxz4ILkuebmq6UFD9R68ms,282
|
|
176
|
+
docket_auth/__init__.py,sha256=T28BUSFiljQsQR8oOK2UxLJHCirg5D9zK6zCCdhT4R8,9166
|
|
177
|
+
docket_mcp/__init__.py,sha256=pOSC3KZ_hkFb0VW4QDDpkGa7lpUPGEMlUUmDd8HCwsg,368
|
|
178
|
+
docket_mcp/api.py,sha256=5eDiTC_lJJGKI02PQ_J1RytdltfllVSN9R93dXK3RE0,8483
|
|
179
|
+
docket_mcp/config.py,sha256=aKS_dc5-r9AeMzbgjHtYiFzBtH8ZTEb-7QsXqhXXpuw,2005
|
|
180
|
+
docket_mcp/server.py,sha256=nqgvV_mWkuDBwe8ORH54PxyHZhLjGtAqpA3yJgHCfh8,2563
|
|
181
|
+
docket_mcp/signin.py,sha256=lwFZ4lodUnaqOWg6ivsWeZVpzdHQD1mr8dwnDIZYb6c,5684
|
|
182
|
+
docket_mcp/slots.py,sha256=SUzQ6vXdgDH_6lwX9T6SMvRV-xo3i62X-oU76MDPNp0,2779
|
|
183
|
+
docket_mcp/times.py,sha256=HpoIaJ3uihwfY0oo3OBk5CJJzuRzCioDULJcXB9fl1E,5820
|
|
184
|
+
docket_mcp/tools.py,sha256=Z4L6WbTS5FlJYFvWVK3IrOXAgADeYqdwtHV4aKnIT60,52360
|
|
185
|
+
docket_client-0.3.2.dist-info/METADATA,sha256=xfL1wU9NPHXyUTYJFfCyoInY2IUBoHEbDdUdMKdbPoQ,10352
|
|
186
|
+
docket_client-0.3.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
187
|
+
docket_client-0.3.2.dist-info/entry_points.txt,sha256=Wh6bZ_A_jc7MClLXlPaufhYtoYMGMLmTMC7ynpolbxk,54
|
|
188
|
+
docket_client-0.3.2.dist-info/RECORD,,
|
docket_mcp/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""A local MCP server for Docket (console script ``docket-mcp``, install ``docket-client[mcp]``).
|
|
2
|
+
|
|
3
|
+
Hand-written, a sibling of ``docket_auth``; ``client/docket/`` is generated and never edited.
|
|
4
|
+
It speaks MCP over stdio and calls the Docket API through the generated client, so nothing new
|
|
5
|
+
is hosted and the institute network is reached from the user's own machine.
|
|
6
|
+
"""
|
docket_mcp/api.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""The HTTP layer: the generated client's connections, token handling and error mapping.
|
|
2
|
+
|
|
3
|
+
Reads go through an anonymous `docket.Client`; only writes use the token-carrying
|
|
4
|
+
[`docket.AuthenticatedClient`][docket.client.AuthenticatedClient] (from ``docket_auth``), mirroring the frontend. ``/api/v1/me``
|
|
5
|
+
is read once to learn ``auth_mode``: when it is ``disabled`` no token is ever requested and
|
|
6
|
+
sign-in is never prompted. Responses stay plain JSON so tools can reshape them for a model.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
from docket import Client
|
|
15
|
+
from docket_auth import AuthenticationError, TokenProvider, authenticated_client
|
|
16
|
+
|
|
17
|
+
from .config import Settings
|
|
18
|
+
from .signin import SignInRequired, build_provider, non_interactive_token
|
|
19
|
+
|
|
20
|
+
__all__ = ["DocketApi", "DocketToolError"]
|
|
21
|
+
|
|
22
|
+
TOKEN_HELP = "Set DOCKET_API_TOKEN (ask an admin for a token from Settings > Access)."
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DocketToolError(Exception):
|
|
26
|
+
"""A failure to report to the model as the tool's error text (never carries a token)."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DocketApi:
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
settings: Settings,
|
|
33
|
+
*,
|
|
34
|
+
transport: httpx.BaseTransport | None = None,
|
|
35
|
+
token_provider: TokenProvider | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
self.settings = settings
|
|
38
|
+
extra: dict[str, Any] = {"transport": transport} if transport else {}
|
|
39
|
+
timeout = httpx.Timeout(30.0)
|
|
40
|
+
self._anonymous = Client(base_url=settings.api_url, timeout=timeout, httpx_args=extra)
|
|
41
|
+
self._provider = token_provider or build_provider(settings)
|
|
42
|
+
self._authed = (
|
|
43
|
+
authenticated_client(
|
|
44
|
+
settings.api_url, self._provider, timeout=timeout, httpx_args=dict(extra)
|
|
45
|
+
)
|
|
46
|
+
if self._provider
|
|
47
|
+
else None
|
|
48
|
+
)
|
|
49
|
+
self._me: dict[str, Any] | None = None
|
|
50
|
+
|
|
51
|
+
# -- reads (never carry a token) -------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
def get(self, path: str, *, what: str = "this", **params: Any) -> Any:
|
|
54
|
+
query = {k: v for k, v in params.items() if v is not None}
|
|
55
|
+
return self._send(self._anonymous, "GET", path, what, "read", params=query)
|
|
56
|
+
|
|
57
|
+
def me(self) -> dict[str, Any]:
|
|
58
|
+
if self._me is None:
|
|
59
|
+
self._me = self.get("/api/v1/me", what="the caller")
|
|
60
|
+
assert self._me is not None
|
|
61
|
+
return self._me
|
|
62
|
+
|
|
63
|
+
def me_as_caller(self) -> tuple[dict[str, Any], bool]:
|
|
64
|
+
"""``/me`` with the caller's own credential when one is available without interaction.
|
|
65
|
+
|
|
66
|
+
Returns ``(me, signed_in)``. An API token, client credentials or a warm msal cache are
|
|
67
|
+
used; a device-code sign-in is never started here. Falls back to the anonymous view.
|
|
68
|
+
"""
|
|
69
|
+
anonymous = self.me()
|
|
70
|
+
if (
|
|
71
|
+
anonymous.get("auth_mode") == "disabled"
|
|
72
|
+
or self._provider is None
|
|
73
|
+
or self._authed is None
|
|
74
|
+
):
|
|
75
|
+
return anonymous, False
|
|
76
|
+
if non_interactive_token(self._provider) is None:
|
|
77
|
+
return anonymous, False
|
|
78
|
+
try:
|
|
79
|
+
return self._send(self._authed, "GET", "/api/v1/me", "the caller", "read"), True
|
|
80
|
+
except DocketToolError:
|
|
81
|
+
return anonymous, False # rejected or unreachable: show the anonymous view
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def auth_mode(self) -> str:
|
|
85
|
+
return str(self.me().get("auth_mode", "disabled"))
|
|
86
|
+
|
|
87
|
+
# -- writes (carry a token unless the server has auth disabled) ------------------------------
|
|
88
|
+
|
|
89
|
+
def post(
|
|
90
|
+
self,
|
|
91
|
+
path: str,
|
|
92
|
+
body: Any = None,
|
|
93
|
+
*,
|
|
94
|
+
action: str,
|
|
95
|
+
idempotency_key: str | None = None,
|
|
96
|
+
**params: Any,
|
|
97
|
+
) -> Any:
|
|
98
|
+
headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
|
|
99
|
+
return self._write("POST", path, body, action, headers, params)
|
|
100
|
+
|
|
101
|
+
def patch(self, path: str, body: Any, *, action: str) -> Any:
|
|
102
|
+
return self._write("PATCH", path, body, action, None, {})
|
|
103
|
+
|
|
104
|
+
def preview(self, path: str, body: Any = None) -> Any:
|
|
105
|
+
"""A server preview endpoint: it writes nothing, so it carries no token."""
|
|
106
|
+
return self._send(self._anonymous, "POST", path, "this", "preview", json=body)
|
|
107
|
+
|
|
108
|
+
def _write(
|
|
109
|
+
self,
|
|
110
|
+
method: str,
|
|
111
|
+
path: str,
|
|
112
|
+
body: Any,
|
|
113
|
+
action: str,
|
|
114
|
+
headers: dict[str, str] | None,
|
|
115
|
+
params: dict[str, Any],
|
|
116
|
+
) -> Any:
|
|
117
|
+
client = self._anonymous if self.auth_mode == "disabled" else self._credentialed()
|
|
118
|
+
query = {k: v for k, v in params.items() if v is not None}
|
|
119
|
+
return self._send(
|
|
120
|
+
client, method, path, "this", action, json=body, headers=headers, params=query
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def _credentialed(self) -> Client:
|
|
124
|
+
if self._authed is None or self._provider is None:
|
|
125
|
+
if self.auth_mode == "tokens":
|
|
126
|
+
raise DocketToolError(TOKEN_HELP)
|
|
127
|
+
raise DocketToolError(
|
|
128
|
+
"This Docket needs a signed-in caller for changes. "
|
|
129
|
+
f"{TOKEN_HELP} Or sign in as yourself: set DOCKET_AUTH_CLIENT_ID, DOCKET_AUTH_SCOPE "
|
|
130
|
+
"and DOCKET_AUTH_TENANT_ID (or DOCKET_AUTH_AUTHORITY)."
|
|
131
|
+
)
|
|
132
|
+
return self._authed # type: ignore[return-value]
|
|
133
|
+
|
|
134
|
+
# -- plumbing --------------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
def _send(
|
|
137
|
+
self, client: Any, method: str, path: str, what: str, action: str, **kwargs: Any
|
|
138
|
+
) -> Any:
|
|
139
|
+
try:
|
|
140
|
+
response = client.get_httpx_client().request(method, path, **kwargs)
|
|
141
|
+
except SignInRequired as error:
|
|
142
|
+
raise DocketToolError(str(error)) from None
|
|
143
|
+
except AuthenticationError as error:
|
|
144
|
+
raise DocketToolError(f"Could not get a token: {error}") from None
|
|
145
|
+
except httpx.TimeoutException:
|
|
146
|
+
raise DocketToolError(
|
|
147
|
+
f"Docket at {self.settings.api_url} did not answer in time."
|
|
148
|
+
) from None
|
|
149
|
+
except httpx.HTTPError as error:
|
|
150
|
+
raise DocketToolError(
|
|
151
|
+
f"Cannot reach Docket at {self.settings.api_url} ({type(error).__name__}). "
|
|
152
|
+
"Is the server running, and are you on the VPN? Check DOCKET_API_URL."
|
|
153
|
+
) from None
|
|
154
|
+
if response.status_code >= 400:
|
|
155
|
+
raise _problem(response, action)
|
|
156
|
+
if not response.content:
|
|
157
|
+
return None
|
|
158
|
+
return response.json()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _problem(response: httpx.Response, action: str) -> DocketToolError:
|
|
162
|
+
"""Map an RFC 9457 problem response to a message the model can act on."""
|
|
163
|
+
detail = ""
|
|
164
|
+
code = ""
|
|
165
|
+
errors: Any = None
|
|
166
|
+
try:
|
|
167
|
+
body = response.json()
|
|
168
|
+
detail = str(body.get("detail") or body.get("title") or "")
|
|
169
|
+
code = str(body.get("code") or "")
|
|
170
|
+
errors = body.get("errors")
|
|
171
|
+
except ValueError:
|
|
172
|
+
detail = response.text[:200]
|
|
173
|
+
status = response.status_code
|
|
174
|
+
suffix = f" ({detail})" if detail else ""
|
|
175
|
+
if status == 401:
|
|
176
|
+
return DocketToolError(
|
|
177
|
+
f"Not signed in, or the credential was rejected{suffix}. "
|
|
178
|
+
"If you use an API token it may be revoked or expired: "
|
|
179
|
+
"ask an admin for a new one from Settings > Access."
|
|
180
|
+
)
|
|
181
|
+
if status == 403:
|
|
182
|
+
return DocketToolError(
|
|
183
|
+
f"You are not allowed to {action}{suffix}. "
|
|
184
|
+
"Sessions can usually only be changed by their creator, a schedule maintainer or an admin."
|
|
185
|
+
)
|
|
186
|
+
if status == 404:
|
|
187
|
+
return DocketToolError(f"Not found{suffix}. Check the id and try again.")
|
|
188
|
+
if status == 409:
|
|
189
|
+
return DocketToolError(
|
|
190
|
+
f"This changed since your preview, or conflicts with another change{suffix}. "
|
|
191
|
+
"Preview again (dry_run=true), show the user, and confirm that one."
|
|
192
|
+
)
|
|
193
|
+
if status in (400, 422):
|
|
194
|
+
fields = ""
|
|
195
|
+
if isinstance(errors, dict) and errors:
|
|
196
|
+
fields = " Fields: " + "; ".join(
|
|
197
|
+
f"{key}: {', '.join(map(str, value)) if isinstance(value, list) else value}"
|
|
198
|
+
for key, value in errors.items()
|
|
199
|
+
)
|
|
200
|
+
return DocketToolError(f"The server rejected the request{suffix}.{fields}")
|
|
201
|
+
if status == 429:
|
|
202
|
+
return DocketToolError(f"Too many requests{suffix}; wait a moment and retry.")
|
|
203
|
+
if status == 502 or status == 503 or code == "upstream_unavailable":
|
|
204
|
+
return DocketToolError(
|
|
205
|
+
f"Docket could not reach an upstream service{suffix}. Try again later."
|
|
206
|
+
)
|
|
207
|
+
return DocketToolError(f"Docket returned an unexpected error {status}{suffix}.")
|