oth-mcp 0.1.3 → 0.1.5

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.
package/README.md CHANGED
@@ -13,12 +13,15 @@ The server is designed for minimal token usage. Instead of embedding the full AP
13
13
 
14
14
  ## Setup
15
15
 
16
+ **Defaults with no env set:** `OTH_ENV=dev` → base URL `https://doccla-dev.oth.io`, credentials read from Keychain account `oth-dev`. Override `OTH_ENV` (`dev`, `stag`, `demo`, `local`) to target another environment — the Keychain account name follows as `oth-<env>`.
17
+
16
18
  ### 1. Configure credentials
17
19
 
18
20
  **macOS Keychain (preferred)** — no plaintext on disk:
19
21
 
20
22
  ```bash
21
- # For each environment (oth-dev, oth-stag, oth-demo):
23
+ # Account name is oth-<env>, so repeat per env you use
24
+ # (oth-dev, oth-stag, oth-demo, oth-local):
22
25
  security add-generic-password -a "oth-dev" -s "oth-mcp-username" -w
23
26
  security add-generic-password -a "oth-dev" -s "oth-mcp-password" -w
24
27
  ```
@@ -31,12 +34,47 @@ export OTH_PASSWORD="your-password"
31
34
  export OTH_ENV="dev" # optional, defaults to dev
32
35
  ```
33
36
 
34
- ### 2. Add to Claude Code
37
+ ### 2. Add to Claude Code (no env default dev)
35
38
 
36
39
  ```bash
37
40
  claude mcp add oth -- npx -y oth-mcp
38
41
  ```
39
42
 
43
+ Pass env vars with `-e KEY=VALUE` (repeatable). Example pointing at a local opentele stack with inline creds:
44
+
45
+ ```bash
46
+ claude mcp add oth \
47
+ -e OTH_ENV=local \
48
+ -e OTH_USERNAME=admin \
49
+ -e OTH_PASSWORD=admin_23 \
50
+ -- npx -y oth-mcp
51
+ ```
52
+
53
+ Omit `OTH_USERNAME`/`OTH_PASSWORD` if you've stored them in Keychain under `oth-local`. Use `-e OTH_BASE_URL=http://host.docker.internal:7100` if the default `http://localhost:7100` isn't reachable.
54
+
55
+ ### Local OTH (docker)
56
+
57
+ To point the MCP at a local opentele stack run via docker:
58
+
59
+ ```bash
60
+ # in the opentele repo:
61
+ cd dev && docker compose up -d --wait
62
+
63
+ # then launch the MCP with:
64
+ export OTH_ENV=local # → http://localhost:7100
65
+ export OTH_USERNAME=admin
66
+ export OTH_PASSWORD=admin_23 # or any dev account — see opentele docs
67
+ ```
68
+
69
+ Or store local creds in Keychain under the `oth-local` account:
70
+
71
+ ```bash
72
+ security add-generic-password -a "oth-local" -s "oth-mcp-username" -w
73
+ security add-generic-password -a "oth-local" -s "oth-mcp-password" -w
74
+ ```
75
+
76
+ Set `OTH_BASE_URL` to override the host/port (e.g. `http://host.docker.internal:7100`) if you're not on the default.
77
+
40
78
  ## Tools
41
79
 
42
80
  ### `oth_get`
@@ -1 +1 @@
1
- export declare const API_GUIDE = "# OTH API Endpoint Reference\n\n## Key Concepts\n\nLinks are the API's primary navigation mechanism. Every response includes a\nlinks object with absolute URIs. Use these as-is for subsequent requests \u2014\nnever construct URLs from IDs manually. Foreign key references (e.g. patient\nin a measurement, patientGroups in a patient) are always link URIs, not bare IDs.\n\nAuth: the MCP server handles auth transparently. For reference, most read\nendpoints and questionnaire submission accept Basic auth. Acknowledgements,\nignored, and manual measurement-captures require a clinician-scoped Bearer JWT\nobtained via POST /idp2/tokens.\n\n## Common Workflows\n\nReview results:\n GET /clinician/api/patients?uniqueId={nhsNo}\n GET /results/results?patient={patient-link}&acknowledged=false\n GET {result-link}\n PUT {result-link}/acknowledgement (Bearer JWT required)\n\nSubmit questionnaire response:\n GET /clinician/api/questionnaire_definitions/{id}\n GET {links.activeQuestionnaire} (extract node names from response)\n POST {questionnaire-link}/results\n {version:\"1.0\", date:ISO-8601, links:{patient:uri},\n output:[{name:\"{nodeName}.FIELD\", type:\"Integer\"|\"Float\"|\"Object\"|\"Boolean\", value:<typed>}]}\n\nSubmit measurement (clinician-entered):\n POST /measurements/measurement-captures\n {source, sourceId, captureTime, links:{patient:uri},\n measurements:[{type, timestamp, measurement:{<type-specific>},\n origin:{manualMeasurement:{enteredBy} | deviceMeasurement:{connectionType,manufacturer,model,primaryDeviceIdentifier}}}]}\n Common types: blood_pressure({systolic,diastolic,unit}), pulse({value,unit}),\n saturation({value,unit}), temperature({value,unit}), weight(Float)\n\nPatient lifecycle:\n POST /clinician/api/patients (with links.patientGroups)\n GET+PUT /organizations/patients/{id}/metadata (PUT replaces entire set \u2014 GET first, merge, then PUT)\n PUT /clinician/api/patients/{id} (status:\"discharged\")\n\n## Patients\nGET /clinician/api/patients ?uniqueId= &username= &firstName= &lastName= &search= &phone= &patientGroup= &id= &status= &select= &max= &offset=\nGET /clinician/api/patients/{id}\nGET /clinician/api/patients/me\nGET /clinician/api/patients/{id}/measurements ?from= &to= &max=\nGET /clinician/api/patients/{id}/measurements/aggregate\nGET /clinician/api/patients/{id}/measurement-types\nGET /clinician/api/patients/{id}/questionnaires\nGET /clinician/api/patients/{id}/questionnaire_schedules\nPOST /clinician/api/patients/{id}/questionnaire_schedules\nGET /clinician/api/patients/{id}/contact-info\nPUT /clinician/api/patients/{id}/contact-info\nGET /clinician/api/patients/{id}/acknowledgements\n status enum: all|active|inactive|discharged|discharged_self_managed|deceased (default: active)\n patientGroup and id are repeatable (up to 100\u00D7) for batch queries\n search matches across firstName, lastName, uniqueId, username\n\n## Clinicians\nGET /clinician/api/clinicians ?max= &offset=\nPOST /clinician/api/clinicians\nGET /clinician/api/clinicians/{id}\nPUT /clinician/api/clinicians/{id}\nGET /clinician/api/clinicians/me\nGET /clinician/api/clinicians/me/colleagues\n\n## Questionnaire Definitions\nGET /clinician/api/questionnaire_definitions ?max= &offset=\nGET /clinician/api/questionnaire_definitions/{id}\nPOST /clinician/api/questionnaire_definitions {name, requiresManualInspection}\nPUT /clinician/api/questionnaire_definitions/{id}\n\n## Questionnaires (node graph)\nGET /clinician/api/questionnaires/{id}\nPOST /clinician/api/questionnaires/{id}/results\n\n## Results\nGET /results/results ?patient= &acknowledged= &resultType= &questionnaire= &order= &from= &to= &max=\nGET /results/results/{uuid}\nPUT /results/results/{uuid}/acknowledgement (Bearer JWT required)\nPUT /results/results/{uuid}/ignored (Bearer JWT required)\n\n## Questionnaire Schedules\nGET /clinician/api/questionnaire_schedules ?max= &offset=\n\n## Questionnaire Groups\nGET /questionnaires/questionnaire-groups ?max=\nPOST /questionnaires/questionnaire-groups {name, questionnaireDefinitions}\n\n## Patient Notes\nGET /clinician/api/patient-notes ?max= &offset=\nPOST /clinician/api/patient-notes\nGET /clinician/api/patient-notes/{id}\nPUT /clinician/api/patient-notes/{id}\nDELETE /clinician/api/patient-notes/{id}\nPOST /clinician/api/patient-notes/{id}/read\n\n## Measurements\nGET /measurements/measurement-types\nGET /measurements/measurement-captures ?patient= &source= &since= &from= &to= &max=\nPOST /measurements/measurement-captures\nGET /measurements/measurement-captures/{uuid}\nPOST /measurements/measurement-captures/{uuid}/measurements\nGET /measurements/measurement-streams ?patient= &patientGroup= &from= &to= &max=\nPOST /measurements/measurement-streams\nGET /measurements/measurement-streams/{uuid}\nGET /measurements/measurement-streams/{uuid}/measurements\nGET /measurements/measurement-streams/{uuid}/incidents\nGET /measurements/measurement-streams/incidents\nPOST /measurements/measurement-streams/acknowledgements\nGET /measurements/processors\nGET /measurements/processors/{name}\n\n## Thresholds\nGET /thresholds/patient-thresholds ?patient= &max=\nPOST /thresholds/patient-thresholds\nGET /thresholds/patient-thresholds/{uuid}\nPUT /thresholds/patient-thresholds/{uuid}\nDELETE /thresholds/patient-thresholds/{uuid}\nGET /thresholds/patient-group-thresholds ?max=\nPOST /thresholds/patient-group-thresholds\nGET /thresholds/patient-group-thresholds/{uuid}\nPUT /thresholds/patient-group-thresholds/{uuid}\nDELETE /thresholds/patient-group-thresholds/{uuid}\nPOST /thresholds/evaluation\n\n## Organizations\nGET /organizations/organizations\nGET /organizations/organizations/{uuid}\nPUT /organizations/organizations/{uuid}\nGET /organizations/organizations/{uuid}/metadata-fields\nGET /organizations/patients/{id}/metadata\nPUT /organizations/patients/{id}/metadata (replaces entire set \u2014 GET first, merge, then PUT)\n\n## Object Storage\nPOST /object-storage/objects (raw body, set Content-Type header)\nGET /object-storage/objects/{uuid}\n\n## Users & IDP\nPOST /idp2/tokens (Basic auth, body: {links:{clinician:uri}}) \u2192 201 {token}\nGET /idp2/users/{id}\nPUT /idp2/users/{id}/locked\nDELETE /idp2/users/{id}/password\nGET /idp2/roles\nGET /idp2/permissions\n\n## Patient Groups\nGET /clinician/api/patientgroups ?max= &offset=\nGET /clinician/api/patientgroups/{id}\nPOST /clinician/api/patientgroups\nPUT /clinician/api/patientgroups/{id}\nDELETE /clinician/api/patientgroups/{id}\n\n## Audit\nGET /audit/resources/{resourceUrl} ?max= &offset=\n\n## Calendar\nGET /calendar/events ?patient= &from= &to=\nPOST /calendar/events\nGET /calendar/events/{uuid}\nPUT /calendar/events/{uuid}\nDELETE /calendar/events/{uuid}\n\n## Chat\nGET /chat/threads ?patient=\nGET /chat/threads/{uuid}\nGET /chat/threads/{uuid}/messages\nPOST /chat/threads/{uuid}/read\nPOST /chat/messages\nGET /chat/messages/{uuid}\nPOST /chat/messages/{uuid}/read\nGET /chat/snippets\nPOST /chat/snippets\nPUT /chat/snippets/{uuid}\nDELETE /chat/snippets/{uuid}\n\n## Notifications\nGET /notifications/devices ?user=\nPOST /notifications/devices\nGET /notifications/subscriptions\nPOST /notifications/subscriptions\nDELETE /notifications/subscriptions/{uuid}\n\n## Mutation Request Bodies\n\nPOST /clinician/api/patients\n required: uniqueId, username, firstName, lastName, sex(male|female|unknown),\n address, postalCode, city, links.patientGroups[uri...]\n optional: dateOfBirth(YYYY-MM-DD), phone, mobilePhone, email, comment,\n dueDate, profilePicture, place, relatives[{firstName,lastName,relation,phone}]\n\nPUT /clinician/api/patients/{id}\n required: uniqueId, username, firstName, lastName, sex, address, postalCode, city,\n status(active|inactive|discharged|discharged_self_managed|deceased),\n links.patientGroups[uri...]\n optional: dateOfBirth, phone, mobilePhone, email, comment, dueDate, profilePicture,\n place, relatives[{firstName,lastName,relation,phone}]\n\nPOST /clinician/api/clinicians\n required: username, firstName, lastName, links.patientGroups[uri...], links.roles[uri...]\n optional: email, phone, mobilePhone, uniqueId\n\nPOST /clinician/api/patient-notes\n required: note, type(normal|important), links.patient(uri)\n optional: reminderDate(date-time)\n\nPOST /clinician/api/patientgroups\n required: name, messagingEnabled, blueAlarmsEnabled, links.organization(uri)\n optional: dueDateVisible, calculateBloodPressureWeeklyAverage, copdPrediction\n\nPOST /clinician/api/patients/{id}/questionnaire_schedules\n required: links.questionnaireDefinition(uri)\n optional: scheduledTime (varies by type \u2014 WEEKDAYS, MONTHLY, EVERY_NTH_DAY, SPECIFIC_DATE, etc.)\n\nPOST /measurements/measurement-captures\n required: source, sourceId, captureTime(date-time), links.patient(uri),\n measurements[{type, timestamp, measurement:{<type-specific fields>},\n origin:{manualMeasurement:{enteredBy} | deviceMeasurement:{connectionType,manufacturer,model,primaryDeviceIdentifier}}}]\n optional: historic\n note: source+sourceId must be unique per capture\n\nPOST /measurements/measurement-streams\n required: inputType(e.g. \"discrete\"), links.patient(uri),\n measurements[{type, timestamp, measurement:{value,unit},\n origin:{deviceMeasurement:{connectionType,manufacturer,model,primaryDeviceIdentifier:{macAddress}}}}]\n\nPUT /results/results/{uuid}/acknowledgement\n required: note, date(date-time), visibleForPatient(bool)\n optional: closureNotes[string...]\n\nPUT /results/results/{uuid}/ignored\n required: replyIds[string...] (empty array to unignore)\n optional: reason\n\nPOST /chat/messages\n required: body, links.organization(uri)\n variant 1: links.patient(uri), optional links.attachments[uri...]\n variant 2: links.patientGroup(uri)\n\nPOST /calendar/events\n required: type, description, schedule.startTime(date-time),\n party.clinicians[{links.clinician(uri)}],\n party.patients[{links.patient(uri)}],\n links.origin(uri)\n optional: schedule.endTime(date-time), party.name\n\n## Notes\n- Dates use ISO 8601: 2024-01-01T00:00:00.000Z\n- Default max is 100, default offset is 0\n- All list endpoints support pagination via max/offset\n- Clinician-service resources use numeric {id}; other services use {uuid}\n- Links are always absolute URIs: https://doccla-dev.oth.io/clinician/api/patients/123\n- Schemas discoverable via each service's root links.schemas; filenames vary by endpoint\n";
1
+ export declare const API_GUIDE = "# OTH API Endpoint Reference\n\n## Key Concepts\n\nLinks are the API's primary navigation mechanism. Every response includes a\nlinks object with absolute URIs. Use these as-is for subsequent requests \u2014\nnever construct URLs from IDs manually. Foreign key references (e.g. patient\nin a measurement, patientGroups in a patient) are always link URIs, not bare IDs.\n\nAuth: the MCP server handles auth transparently. For reference, most read\nendpoints and questionnaire submission accept Basic auth. Acknowledgements,\nignored, and manual measurement-captures require a clinician-scoped Bearer JWT\nobtained via POST /idp2/tokens.\n\n## Common Workflows\n\nReview results:\n GET /clinician/api/patients?uniqueId={nhsNo}\n GET /results/results?patient={patient-link}&acknowledged=false\n GET {result-link}\n PUT {result-link}/acknowledgement (Bearer JWT required)\n\nSubmit questionnaire response:\n GET /clinician/api/questionnaire_definitions/{id}\n GET {links.activeQuestionnaire} (extract node names from response)\n POST {questionnaire-link}/results\n {version:\"1.0\", date:ISO-8601, links:{patient:uri},\n output:[{name:\"{nodeName}.FIELD\", type:\"Integer\"|\"Float\"|\"Object\"|\"Boolean\", value:<typed>}]}\n\nSubmit measurement (clinician-entered):\n POST /measurements/measurement-captures\n {source, sourceId, captureTime, links:{patient:uri},\n measurements:[{type, timestamp, measurement:{<type-specific>},\n origin:{manualMeasurement:{enteredBy} | deviceMeasurement:{connectionType,manufacturer,model,primaryDeviceIdentifier}}}]}\n Common types: blood_pressure({systolic,diastolic,unit}), pulse({value,unit}),\n saturation({value,unit}), temperature({value,unit}), weight(Float)\n\nPatient lifecycle:\n POST /clinician/api/patients (with links.patientGroups)\n GET+PUT /organizations/patients/{id}/metadata (PUT replaces entire set \u2014 GET first, merge, then PUT)\n PUT /clinician/api/patients/{id} (status:\"discharged\")\n\n## Patients\nGET /clinician/api/patients ?uniqueId= &username= &firstName= &lastName= &search= &phone= &patientGroup= &id= &status= &select= &max= &offset=\nGET /clinician/api/patients/{id}\nGET /clinician/api/patients/me\nGET /clinician/api/patients/{id}/measurements ?from= &to= &max=\nGET /clinician/api/patients/{id}/measurements/aggregate\nGET /clinician/api/patients/{id}/measurement-types\nGET /clinician/api/patients/{id}/questionnaires\nGET /clinician/api/patients/{id}/questionnaire_schedules\nPOST /clinician/api/patients/{id}/questionnaire_schedules\nGET /clinician/api/patients/{id}/contact-info\nPUT /clinician/api/patients/{id}/contact-info\nGET /clinician/api/patients/{id}/acknowledgements\n status enum: all|active|inactive|discharged|discharged_self_managed|deceased (default: active)\n patientGroup and id are repeatable (up to 100\u00D7) for batch queries\n search matches across firstName, lastName, uniqueId, username\n\n## Clinicians\nGET /clinician/api/clinicians ?max= &offset=\nPOST /clinician/api/clinicians\nGET /clinician/api/clinicians/{id}\nPUT /clinician/api/clinicians/{id}\nGET /clinician/api/clinicians/me\nGET /clinician/api/clinicians/me/colleagues\n\n## Questionnaire Definitions\nGET /clinician/api/questionnaire_definitions ?max= &offset=\nGET /clinician/api/questionnaire_definitions/{id}\nPOST /clinician/api/questionnaire_definitions {name, requiresManualInspection}\nPUT /clinician/api/questionnaire_definitions/{id}\n\n## Questionnaires (node graph)\nGET /clinician/api/questionnaires/{id}\nPOST /clinician/api/questionnaires/{id}/results\n\n## Results\nGET /results/results ?patient= &acknowledged= &resultType= &questionnaire= &order= &from= &to= &max=\nGET /results/results/{uuid}\nPUT /results/results/{uuid}/acknowledgement (Bearer JWT required)\nPUT /results/results/{uuid}/ignored (Bearer JWT required)\n\n## Questionnaire Schedules\nGET /clinician/api/questionnaire_schedules ?max= &offset=\n\n## Questionnaire Groups\nGET /questionnaires/questionnaire-groups ?max=\nPOST /questionnaires/questionnaire-groups {name, questionnaireDefinitions}\n\n## Patient Notes\nGET /clinician/api/patient-notes ?max= &offset=\nPOST /clinician/api/patient-notes\nGET /clinician/api/patient-notes/{id}\nPUT /clinician/api/patient-notes/{id}\nDELETE /clinician/api/patient-notes/{id}\nPOST /clinician/api/patient-notes/{id}/read\n\n## Measurements\nGET /measurements/measurement-types\nGET /measurements/measurement-captures ?patient= &source= &since= &from= &to= &max=\nPOST /measurements/measurement-captures\nGET /measurements/measurement-captures/{uuid}\nPOST /measurements/measurement-captures/{uuid}/measurements\nGET /measurements/measurement-streams ?patient= &patientGroup= &from= &to= &max=\nPOST /measurements/measurement-streams\nGET /measurements/measurement-streams/{uuid}\nGET /measurements/measurement-streams/{uuid}/measurements\nGET /measurements/measurement-streams/{uuid}/incidents\nGET /measurements/measurement-streams/incidents\nPOST /measurements/measurement-streams/acknowledgements\nGET /measurements/processors\nGET /measurements/processors/{name}\n\n## Thresholds\nGET /thresholds/patient-thresholds ?patient= &max=\nPOST /thresholds/patient-thresholds\nGET /thresholds/patient-thresholds/{uuid}\nPUT /thresholds/patient-thresholds/{uuid}\nDELETE /thresholds/patient-thresholds/{uuid}\nGET /thresholds/patient-group-thresholds ?max=\nPOST /thresholds/patient-group-thresholds\nGET /thresholds/patient-group-thresholds/{uuid}\nPUT /thresholds/patient-group-thresholds/{uuid}\nDELETE /thresholds/patient-group-thresholds/{uuid}\nPOST /thresholds/evaluation\n\n## Organizations\nGET /organizations/organizations\nGET /organizations/organizations/{uuid}\nPUT /organizations/organizations/{uuid}\nGET /organizations/organizations/{uuid}/metadata-fields\nGET /organizations/patients/{id}/metadata\nPUT /organizations/patients/{id}/metadata (replaces entire set \u2014 GET first, merge, then PUT)\n\n## Object Storage\nPOST /object-storage/objects (raw body, set Content-Type header)\nGET /object-storage/objects/{uuid}\n\n## Users & IDP\nPOST /idp2/tokens (Basic auth, body: {links:{clinician:uri}}) \u2192 201 {token}\nGET /idp2/users/{id}\nPUT /idp2/users/{id}/locked\nDELETE /idp2/users/{id}/password\nGET /idp2/roles\nGET /idp2/permissions\n\n## Patient Groups\nGET /clinician/api/patientgroups ?max= &offset=\nGET /clinician/api/patientgroups/{id}\nPOST /clinician/api/patientgroups\nPUT /clinician/api/patientgroups/{id}\nDELETE /clinician/api/patientgroups/{id}\n\n## Audit\nGET /audit/resources/{resourceUrl} ?max= &offset=\n\n## Calendar\nGET /calendar/events ?patient= &from= &to=\nPOST /calendar/events\nGET /calendar/events/{uuid}\nPUT /calendar/events/{uuid}\nDELETE /calendar/events/{uuid}\n\n## Chat\nGET /chat/threads ?patient=\nGET /chat/threads/{uuid}\nGET /chat/threads/{uuid}/messages\nPOST /chat/threads/{uuid}/read\nPOST /chat/messages\nGET /chat/messages/{uuid}\nPOST /chat/messages/{uuid}/read\nGET /chat/snippets\nPOST /chat/snippets\nPUT /chat/snippets/{uuid}\nDELETE /chat/snippets/{uuid}\n\n## Notifications\nGET /notifications/devices ?user=\nPOST /notifications/devices\nGET /notifications/subscriptions\nPOST /notifications/subscriptions\nDELETE /notifications/subscriptions/{uuid}\n\n## Mutation Request Bodies\n\nPOST /clinician/api/patients\n required: uniqueId, username, firstName, lastName, sex(male|female|unknown),\n address, postalCode, city, links.patientGroups[uri...]\n optional: dateOfBirth(YYYY-MM-DD), phone, mobilePhone, email, comment,\n dueDate, profilePicture, place, relatives[{firstName,lastName,relation,phone}]\n\nPUT /clinician/api/patients/{id}\n required: uniqueId, username, firstName, lastName, sex, address, postalCode, city,\n status(active|inactive|discharged|discharged_self_managed|deceased),\n links.patientGroups[uri...]\n optional: dateOfBirth, phone, mobilePhone, email, comment, dueDate, profilePicture,\n place, relatives[{firstName,lastName,relation,phone}]\n note: PUT shape differs from GET \u2014 GET returns patientGroups as nested objects and\n links with all navigation URIs; PUT expects only links.patientGroups as a flat\n array of URIs. Omit fields rather than sending null \u2014 some null values are\n stored as the string \"null\". uniqueId must be unique across the system;\n duplicates return 422 \"Patient not replaced\".\n\nPOST /clinician/api/clinicians\n required: username, firstName, lastName, links.patientGroups[uri...], links.roles[uri...]\n optional: email, phone, mobilePhone, uniqueId\n\nPOST /clinician/api/patient-notes\n required: note, type(normal|important), links.patient(uri)\n optional: reminderDate(date-time)\n\nPOST /clinician/api/patientgroups\n required: name, messagingEnabled, blueAlarmsEnabled, links.organization(uri)\n optional: dueDateVisible, calculateBloodPressureWeeklyAverage, copdPrediction\n\nPOST /clinician/api/patients/{id}/questionnaire_schedules\n required: links.questionnaireDefinition(uri)\n optional: scheduledTime (varies by type \u2014 WEEKDAYS, MONTHLY, EVERY_NTH_DAY, SPECIFIC_DATE, etc.)\n\nPOST /measurements/measurement-captures\n required: source, sourceId, captureTime(date-time), links.patient(uri),\n measurements[{type, timestamp, measurement:{<type-specific fields>},\n origin:{manualMeasurement:{enteredBy} | deviceMeasurement:{connectionType,manufacturer,model,primaryDeviceIdentifier}}}]\n optional: historic\n note: source+sourceId must be unique per capture\n\nPOST /measurements/measurement-streams\n required: inputType(e.g. \"discrete\"), links.patient(uri),\n measurements[{type, timestamp, measurement:{value,unit},\n origin:{deviceMeasurement:{connectionType,manufacturer,model,primaryDeviceIdentifier:{macAddress}}}}]\n\nPUT /results/results/{uuid}/acknowledgement\n required: note, date(date-time), visibleForPatient(bool)\n optional: closureNotes[string...]\n\nPUT /results/results/{uuid}/ignored\n required: replyIds[string...] (empty array to unignore)\n optional: reason\n\nPOST /chat/messages\n required: body, links.organization(uri)\n variant 1: links.patient(uri), optional links.attachments[uri...]\n variant 2: links.patientGroup(uri)\n\nPOST /calendar/events\n required: type, description, schedule.startTime(date-time),\n party.clinicians[{links.clinician(uri)}],\n party.patients[{links.patient(uri)}],\n links.origin(uri)\n optional: schedule.endTime(date-time), party.name\n\n## Notes\n- Dates use ISO 8601: 2024-01-01T00:00:00.000Z\n- Default max is 100, default offset is 0\n- All list endpoints support pagination via max/offset\n- Clinician-service resources use numeric {id}; other services use {uuid}\n- Links are always absolute URIs: https://doccla-dev.oth.io/clinician/api/patients/123\n- Schemas discoverable via each service's root links.schemas; filenames vary by endpoint\n";
package/dist/api-guide.js CHANGED
@@ -196,6 +196,11 @@ PUT /clinician/api/patients/{id}
196
196
  links.patientGroups[uri...]
197
197
  optional: dateOfBirth, phone, mobilePhone, email, comment, dueDate, profilePicture,
198
198
  place, relatives[{firstName,lastName,relation,phone}]
199
+ note: PUT shape differs from GET — GET returns patientGroups as nested objects and
200
+ links with all navigation URIs; PUT expects only links.patientGroups as a flat
201
+ array of URIs. Omit fields rather than sending null — some null values are
202
+ stored as the string "null". uniqueId must be unique across the system;
203
+ duplicates return 422 "Patient not replaced".
199
204
 
200
205
  POST /clinician/api/clinicians
201
206
  required: username, firstName, lastName, links.patientGroups[uri...], links.roles[uri...]
package/dist/auth.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
  const execFileAsync = promisify(execFile);
4
+ const ENV_URLS = {
5
+ local: "http://localhost:7100",
6
+ };
4
7
  function envBaseUrl(env) {
5
- return `https://doccla-${env}.oth.io`;
8
+ return ENV_URLS[env] ?? `https://doccla-${env}.oth.io`;
6
9
  }
7
10
  const REFRESH_BUFFER_SECONDS = 60;
8
11
  const AUTH_ERROR_MESSAGES = {
@@ -27,7 +30,7 @@ export class AuthManager {
27
30
  return this.env;
28
31
  }
29
32
  get baseUrl() {
30
- return envBaseUrl(this.env);
33
+ return process.env.OTH_BASE_URL ?? envBaseUrl(this.env);
31
34
  }
32
35
  get isAuthenticated() {
33
36
  return this.state !== null;
package/dist/index.js CHANGED
File without changes
package/dist/tools.js CHANGED
@@ -133,9 +133,9 @@ Read the oth://api-guide resource for full endpoint reference.`,
133
133
  },
134
134
  inputSchema: {
135
135
  environment: z
136
- .enum(["dev", "stag", "demo"])
136
+ .enum(["dev", "stag", "demo", "local"])
137
137
  .optional()
138
- .describe("Target environment. Omit to show current state."),
138
+ .describe("Target environment. Omit to show current state. 'local' targets http://localhost:7100 (opentele docker stack); set OTH_BASE_URL to override."),
139
139
  },
140
140
  }, async ({ environment }) => {
141
141
  if (environment) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oth-mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "MCP server for the OTH (OpenTeleHealth) API",
5
5
  "type": "module",
6
6
  "bin": {