oth-mcp 0.1.5 → 0.1.6
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 +1 -1
- package/dist/api-guide.d.ts +1 -1
- package/dist/api-guide.js +90 -8
- package/dist/auth.d.ts +1 -3
- package/dist/auth.js +9 -3
- package/dist/client.d.ts +1 -1
- package/dist/index.js +0 -0
- package/dist/tools.js +4 -6
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/api-guide.d.ts
CHANGED
|
@@ -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 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";
|
|
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. Follow these rather than constructing URLs\nfrom IDs manually. Foreign key references (e.g. patient in a measurement,\npatientGroups in a patient) are always link URIs, not bare IDs.\n - In request bodies, use link URIs exactly as returned.\n - As the path for oth_get/oth_mutate, strip the origin and pass only the\n path: absolute URLs are rejected. e.g. a link of\n http://host.docker.internal:7100/clinician/api/patients/15\n is requested as path /clinician/api/patients/15\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\nActing user: every call runs as the MCP's configured user (oth_env shows the\nusername), and is bound by that user's roles and patient groups. GET\n/clinician/api/clinicians/me shows both. Patient reads and writes are limited\nto that user's patient groups: e.g. creating a patient in a group the user is\nnot in returns 422 \"Clinician does not have access to patient group\".\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\nSet up an organization, patient group and role:\n POST /organizations/organizations {name, metadataFields:[]}\n POST /clinician/api/patientgroups (links.organization from the org's links.self)\n POST /thresholds/patient-group-thresholds (optional, per measurement type)\n POST /idp2/roles {name, permissions:[\"ROLE_...\"]}\n PUT /clinician/api/clinicians/{me-id} add the new group to the acting user,\n so patients can be created in it\n\nCreate a clinician or patient that can log in:\n POST /clinician/api/clinicians or POST /clinician/api/patients\n \u2192 response includes temporaryPassword; the account cannot be used until\n its password is changed\n POST /idp2/users/auth as that user (see Users & IDP): NOT possible through\n the MCP, which always authenticates as its own user. Do it outside the MCP,\n with a single request: repeated failed logins lock the account.\n\nCreate and publish a questionnaire:\n POST /clinician/api/questionnaire_definitions {name, requiresManualInspection}\n \u2192 links.self ends in the definition's numeric id\n POST /clinician/api/questionnaire_definitions/{id}/draft\n {active:true, nodes, connections, links:{creator:clinician-uri}}\n \u2192 active:true publishes immediately (response has links.activeQuestionnaire);\n active:false leaves a draft (links.draftQuestionnaire)\n\nCheck or fix a user's login state (no login attempt needed):\n GET /idp2/users?url={clinician-or-patient-uri} \u2192 users[].links.user\n GET {links.user} accountLocked, temporaryPassword,\n passwordExpired, passwordLastChanged\n PUT {links.user}/locked {accountLocked:false}\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 ?username= &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, nameShownToPatient?, requiresManualInspection}\nPUT /clinician/api/questionnaire_definitions/{id}\nPOST /clinician/api/questionnaire_definitions/{id}/draft (new version; see Mutation Request Bodies)\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\nPOST /organizations/organizations {name, metadataFields:[]}\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 ?url={clinician-or-patient-uri} (find the idp2 user behind a clinician/patient)\nGET /idp2/users/{id} includes accountLocked, temporaryPassword, passwordExpired\nPUT /idp2/users/{id}/locked {accountLocked:bool}\nDELETE /idp2/users/{id}/password\nPOST /idp2/users/auth change own password: Basic auth as the user with their\n current (e.g. temporary) password, body {password:new}\n \u2192 200. Not usable via the MCP (it authenticates as itself).\nGET /idp2/roles\nPOST /idp2/roles {name, permissions:[\"ROLE_PATIENT_READ\", ...]}\nGET /idp2/permissions permission names to use in roles\n\n Lockout: repeated failed Basic auth attempts lock the account (\"Account locked\n for user ...\"). Check state with GET /idp2/users/{id}, unlock with PUT .../locked.\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 note: the acting user must belong to every group in links.patientGroups (else 422).\n Response includes temporaryPassword (see Common Workflows).\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 note: response includes temporaryPassword (see Common Workflows).\n\nPUT /clinician/api/clinicians/{id}\n send: firstName, lastName, username, email, uniqueId, phone, mobilePhone,\n links.patientGroups[uri...], links.roles[uri...]\n note: REPLACES the clinician. Any field left out is cleared (e.g. omitting\n uniqueId sets it to null). GET first and send every field back; to add\n a group, send the existing group URIs plus the new one (same for roles).\n\nPOST /thresholds/patient-group-thresholds\n required: measurementType(e.g. \"saturation\"), links.patientGroup(uri),\n limits[{name:\"value\", warningLow, warningHigh, alertLow, alertHigh}] (null for unused),\n thresholdType:{scope:\"point\", point:{type:\"absolute\"}}\n\nPOST /clinician/api/questionnaire_definitions/{id}/draft\n required: active(bool: true publishes), nodes, connections\n optional: links.creator(clinician uri)\n nodes: object keyed by node id, e.g.\n {\"start1\":{id:\"start1\", type:\"start\", position:{top,left}},\n \"input2\":{id:\"input2\", type:\"input\", question, shortText, dataType:\"BOOLEAN\", position},\n \"end5\":{id:\"end5\", type:\"end\", position}}\n connections: [{source, target, type:\"normal\"|\"choice\", choiceValue?, severity:\"\"|\"RED\"|..., frozen?}]\n note: this is the questionnaire editor's graph format (what its Export produces).\n GET {links.activeQuestionnaire} returns a different, compiled form\n (IONode/AssignmentNode/EndNode, nodes renumbered, e.g. \"input2\" becomes\n \"31\") that cannot be posted back as a draft. Take output names for\n result submission (e.g. \"31.FIELD\") from the compiled form.\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 (on local they use the gateway's external host, e.g. http://host.docker.internal:7100,\n which can differ from the MCP's base URL; that is fine, use them as described above)\n- Schemas discoverable via each service's root links.schemas; filenames vary by endpoint\n";
|
package/dist/api-guide.js
CHANGED
|
@@ -3,15 +3,26 @@ export const API_GUIDE = `# OTH API Endpoint Reference
|
|
|
3
3
|
## Key Concepts
|
|
4
4
|
|
|
5
5
|
Links are the API's primary navigation mechanism. Every response includes a
|
|
6
|
-
links object with absolute URIs.
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
links object with absolute URIs. Follow these rather than constructing URLs
|
|
7
|
+
from IDs manually. Foreign key references (e.g. patient in a measurement,
|
|
8
|
+
patientGroups in a patient) are always link URIs, not bare IDs.
|
|
9
|
+
- In request bodies, use link URIs exactly as returned.
|
|
10
|
+
- As the path for oth_get/oth_mutate, strip the origin and pass only the
|
|
11
|
+
path: absolute URLs are rejected. e.g. a link of
|
|
12
|
+
http://host.docker.internal:7100/clinician/api/patients/15
|
|
13
|
+
is requested as path /clinician/api/patients/15
|
|
9
14
|
|
|
10
15
|
Auth: the MCP server handles auth transparently. For reference, most read
|
|
11
16
|
endpoints and questionnaire submission accept Basic auth. Acknowledgements,
|
|
12
17
|
ignored, and manual measurement-captures require a clinician-scoped Bearer JWT
|
|
13
18
|
obtained via POST /idp2/tokens.
|
|
14
19
|
|
|
20
|
+
Acting user: every call runs as the MCP's configured user (oth_env shows the
|
|
21
|
+
username), and is bound by that user's roles and patient groups. GET
|
|
22
|
+
/clinician/api/clinicians/me shows both. Patient reads and writes are limited
|
|
23
|
+
to that user's patient groups: e.g. creating a patient in a group the user is
|
|
24
|
+
not in returns 422 "Clinician does not have access to patient group".
|
|
25
|
+
|
|
15
26
|
## Common Workflows
|
|
16
27
|
|
|
17
28
|
Review results:
|
|
@@ -40,6 +51,36 @@ Patient lifecycle:
|
|
|
40
51
|
GET+PUT /organizations/patients/{id}/metadata (PUT replaces entire set — GET first, merge, then PUT)
|
|
41
52
|
PUT /clinician/api/patients/{id} (status:"discharged")
|
|
42
53
|
|
|
54
|
+
Set up an organization, patient group and role:
|
|
55
|
+
POST /organizations/organizations {name, metadataFields:[]}
|
|
56
|
+
POST /clinician/api/patientgroups (links.organization from the org's links.self)
|
|
57
|
+
POST /thresholds/patient-group-thresholds (optional, per measurement type)
|
|
58
|
+
POST /idp2/roles {name, permissions:["ROLE_..."]}
|
|
59
|
+
PUT /clinician/api/clinicians/{me-id} add the new group to the acting user,
|
|
60
|
+
so patients can be created in it
|
|
61
|
+
|
|
62
|
+
Create a clinician or patient that can log in:
|
|
63
|
+
POST /clinician/api/clinicians or POST /clinician/api/patients
|
|
64
|
+
→ response includes temporaryPassword; the account cannot be used until
|
|
65
|
+
its password is changed
|
|
66
|
+
POST /idp2/users/auth as that user (see Users & IDP): NOT possible through
|
|
67
|
+
the MCP, which always authenticates as its own user. Do it outside the MCP,
|
|
68
|
+
with a single request: repeated failed logins lock the account.
|
|
69
|
+
|
|
70
|
+
Create and publish a questionnaire:
|
|
71
|
+
POST /clinician/api/questionnaire_definitions {name, requiresManualInspection}
|
|
72
|
+
→ links.self ends in the definition's numeric id
|
|
73
|
+
POST /clinician/api/questionnaire_definitions/{id}/draft
|
|
74
|
+
{active:true, nodes, connections, links:{creator:clinician-uri}}
|
|
75
|
+
→ active:true publishes immediately (response has links.activeQuestionnaire);
|
|
76
|
+
active:false leaves a draft (links.draftQuestionnaire)
|
|
77
|
+
|
|
78
|
+
Check or fix a user's login state (no login attempt needed):
|
|
79
|
+
GET /idp2/users?url={clinician-or-patient-uri} → users[].links.user
|
|
80
|
+
GET {links.user} accountLocked, temporaryPassword,
|
|
81
|
+
passwordExpired, passwordLastChanged
|
|
82
|
+
PUT {links.user}/locked {accountLocked:false}
|
|
83
|
+
|
|
43
84
|
## Patients
|
|
44
85
|
GET /clinician/api/patients ?uniqueId= &username= &firstName= &lastName= &search= &phone= &patientGroup= &id= &status= &select= &max= &offset=
|
|
45
86
|
GET /clinician/api/patients/{id}
|
|
@@ -58,7 +99,7 @@ GET /clinician/api/patients/{id}/acknowledgements
|
|
|
58
99
|
search matches across firstName, lastName, uniqueId, username
|
|
59
100
|
|
|
60
101
|
## Clinicians
|
|
61
|
-
GET /clinician/api/clinicians ?max= &offset=
|
|
102
|
+
GET /clinician/api/clinicians ?username= &max= &offset=
|
|
62
103
|
POST /clinician/api/clinicians
|
|
63
104
|
GET /clinician/api/clinicians/{id}
|
|
64
105
|
PUT /clinician/api/clinicians/{id}
|
|
@@ -68,8 +109,9 @@ GET /clinician/api/clinicians/me/colleagues
|
|
|
68
109
|
## Questionnaire Definitions
|
|
69
110
|
GET /clinician/api/questionnaire_definitions ?max= &offset=
|
|
70
111
|
GET /clinician/api/questionnaire_definitions/{id}
|
|
71
|
-
POST /clinician/api/questionnaire_definitions {name, requiresManualInspection}
|
|
112
|
+
POST /clinician/api/questionnaire_definitions {name, nameShownToPatient?, requiresManualInspection}
|
|
72
113
|
PUT /clinician/api/questionnaire_definitions/{id}
|
|
114
|
+
POST /clinician/api/questionnaire_definitions/{id}/draft (new version; see Mutation Request Bodies)
|
|
73
115
|
|
|
74
116
|
## Questionnaires (node graph)
|
|
75
117
|
GET /clinician/api/questionnaires/{id}
|
|
@@ -127,6 +169,7 @@ POST /thresholds/evaluation
|
|
|
127
169
|
|
|
128
170
|
## Organizations
|
|
129
171
|
GET /organizations/organizations
|
|
172
|
+
POST /organizations/organizations {name, metadataFields:[]}
|
|
130
173
|
GET /organizations/organizations/{uuid}
|
|
131
174
|
PUT /organizations/organizations/{uuid}
|
|
132
175
|
GET /organizations/organizations/{uuid}/metadata-fields
|
|
@@ -139,11 +182,19 @@ GET /object-storage/objects/{uuid}
|
|
|
139
182
|
|
|
140
183
|
## Users & IDP
|
|
141
184
|
POST /idp2/tokens (Basic auth, body: {links:{clinician:uri}}) → 201 {token}
|
|
142
|
-
GET /idp2/users
|
|
143
|
-
|
|
185
|
+
GET /idp2/users ?url={clinician-or-patient-uri} (find the idp2 user behind a clinician/patient)
|
|
186
|
+
GET /idp2/users/{id} includes accountLocked, temporaryPassword, passwordExpired
|
|
187
|
+
PUT /idp2/users/{id}/locked {accountLocked:bool}
|
|
144
188
|
DELETE /idp2/users/{id}/password
|
|
189
|
+
POST /idp2/users/auth change own password: Basic auth as the user with their
|
|
190
|
+
current (e.g. temporary) password, body {password:new}
|
|
191
|
+
→ 200. Not usable via the MCP (it authenticates as itself).
|
|
145
192
|
GET /idp2/roles
|
|
146
|
-
|
|
193
|
+
POST /idp2/roles {name, permissions:["ROLE_PATIENT_READ", ...]}
|
|
194
|
+
GET /idp2/permissions permission names to use in roles
|
|
195
|
+
|
|
196
|
+
Lockout: repeated failed Basic auth attempts lock the account ("Account locked
|
|
197
|
+
for user ..."). Check state with GET /idp2/users/{id}, unlock with PUT .../locked.
|
|
147
198
|
|
|
148
199
|
## Patient Groups
|
|
149
200
|
GET /clinician/api/patientgroups ?max= &offset=
|
|
@@ -189,6 +240,8 @@ POST /clinician/api/patients
|
|
|
189
240
|
address, postalCode, city, links.patientGroups[uri...]
|
|
190
241
|
optional: dateOfBirth(YYYY-MM-DD), phone, mobilePhone, email, comment,
|
|
191
242
|
dueDate, profilePicture, place, relatives[{firstName,lastName,relation,phone}]
|
|
243
|
+
note: the acting user must belong to every group in links.patientGroups (else 422).
|
|
244
|
+
Response includes temporaryPassword (see Common Workflows).
|
|
192
245
|
|
|
193
246
|
PUT /clinician/api/patients/{id}
|
|
194
247
|
required: uniqueId, username, firstName, lastName, sex, address, postalCode, city,
|
|
@@ -205,6 +258,33 @@ PUT /clinician/api/patients/{id}
|
|
|
205
258
|
POST /clinician/api/clinicians
|
|
206
259
|
required: username, firstName, lastName, links.patientGroups[uri...], links.roles[uri...]
|
|
207
260
|
optional: email, phone, mobilePhone, uniqueId
|
|
261
|
+
note: response includes temporaryPassword (see Common Workflows).
|
|
262
|
+
|
|
263
|
+
PUT /clinician/api/clinicians/{id}
|
|
264
|
+
send: firstName, lastName, username, email, uniqueId, phone, mobilePhone,
|
|
265
|
+
links.patientGroups[uri...], links.roles[uri...]
|
|
266
|
+
note: REPLACES the clinician. Any field left out is cleared (e.g. omitting
|
|
267
|
+
uniqueId sets it to null). GET first and send every field back; to add
|
|
268
|
+
a group, send the existing group URIs plus the new one (same for roles).
|
|
269
|
+
|
|
270
|
+
POST /thresholds/patient-group-thresholds
|
|
271
|
+
required: measurementType(e.g. "saturation"), links.patientGroup(uri),
|
|
272
|
+
limits[{name:"value", warningLow, warningHigh, alertLow, alertHigh}] (null for unused),
|
|
273
|
+
thresholdType:{scope:"point", point:{type:"absolute"}}
|
|
274
|
+
|
|
275
|
+
POST /clinician/api/questionnaire_definitions/{id}/draft
|
|
276
|
+
required: active(bool: true publishes), nodes, connections
|
|
277
|
+
optional: links.creator(clinician uri)
|
|
278
|
+
nodes: object keyed by node id, e.g.
|
|
279
|
+
{"start1":{id:"start1", type:"start", position:{top,left}},
|
|
280
|
+
"input2":{id:"input2", type:"input", question, shortText, dataType:"BOOLEAN", position},
|
|
281
|
+
"end5":{id:"end5", type:"end", position}}
|
|
282
|
+
connections: [{source, target, type:"normal"|"choice", choiceValue?, severity:""|"RED"|..., frozen?}]
|
|
283
|
+
note: this is the questionnaire editor's graph format (what its Export produces).
|
|
284
|
+
GET {links.activeQuestionnaire} returns a different, compiled form
|
|
285
|
+
(IONode/AssignmentNode/EndNode, nodes renumbered, e.g. "input2" becomes
|
|
286
|
+
"31") that cannot be posted back as a draft. Take output names for
|
|
287
|
+
result submission (e.g. "31.FIELD") from the compiled form.
|
|
208
288
|
|
|
209
289
|
POST /clinician/api/patient-notes
|
|
210
290
|
required: note, type(normal|important), links.patient(uri)
|
|
@@ -256,5 +336,7 @@ POST /calendar/events
|
|
|
256
336
|
- All list endpoints support pagination via max/offset
|
|
257
337
|
- Clinician-service resources use numeric {id}; other services use {uuid}
|
|
258
338
|
- Links are always absolute URIs: https://doccla-dev.oth.io/clinician/api/patients/123
|
|
339
|
+
(on local they use the gateway's external host, e.g. http://host.docker.internal:7100,
|
|
340
|
+
which can differ from the MCP's base URL; that is fine, use them as described above)
|
|
259
341
|
- Schemas discoverable via each service's root links.schemas; filenames vary by endpoint
|
|
260
342
|
`;
|
package/dist/auth.d.ts
CHANGED
|
@@ -11,9 +11,7 @@ export declare class AuthManager {
|
|
|
11
11
|
get environment(): Environment;
|
|
12
12
|
get baseUrl(): string;
|
|
13
13
|
get isAuthenticated(): boolean;
|
|
14
|
-
|
|
15
|
-
username: string | null;
|
|
16
|
-
} | null;
|
|
14
|
+
configuredUsername(): Promise<string | null>;
|
|
17
15
|
switchEnvironment(env: Environment): void;
|
|
18
16
|
getToken(): Promise<string>;
|
|
19
17
|
private isExpiringSoon;
|
package/dist/auth.js
CHANGED
|
@@ -35,10 +35,16 @@ export class AuthManager {
|
|
|
35
35
|
get isAuthenticated() {
|
|
36
36
|
return this.state !== null;
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// The user calls will run as, without authenticating. Null if no credentials are configured.
|
|
39
|
+
async configuredUsername() {
|
|
40
|
+
if (this.state)
|
|
41
|
+
return this.state.username;
|
|
42
|
+
try {
|
|
43
|
+
return (await this.resolveCredentials()).username;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
40
46
|
return null;
|
|
41
|
-
|
|
47
|
+
}
|
|
42
48
|
}
|
|
43
49
|
switchEnvironment(env) {
|
|
44
50
|
if (env === this.env)
|
package/dist/client.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export declare class OthClient {
|
|
|
3
3
|
private auth;
|
|
4
4
|
constructor(auth: AuthManager);
|
|
5
5
|
get(path: string, query?: Record<string, string>): Promise<unknown>;
|
|
6
|
-
mutate(method: "POST" | "PUT" | "PATCH" | "DELETE", path: string, body?: Record<string, unknown>): Promise<unknown>;
|
|
6
|
+
mutate(method: "POST" | "PUT" | "PATCH" | "DELETE", path: string, body?: Record<string, unknown> | unknown[]): Promise<unknown>;
|
|
7
7
|
private validatePath;
|
|
8
8
|
private buildUrl;
|
|
9
9
|
private processResponse;
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/tools.js
CHANGED
|
@@ -89,9 +89,9 @@ Read the oth://api-guide resource for full endpoint reference.`,
|
|
|
89
89
|
.describe("HTTP method"),
|
|
90
90
|
path: z.string().describe("API path"),
|
|
91
91
|
body: z
|
|
92
|
-
.record(z.unknown())
|
|
92
|
+
.union([z.record(z.unknown()), z.array(z.unknown())])
|
|
93
93
|
.optional()
|
|
94
|
-
.describe("Request body (JSON)"),
|
|
94
|
+
.describe("Request body (JSON object or array)"),
|
|
95
95
|
},
|
|
96
96
|
}, async ({ method, path, body }) => {
|
|
97
97
|
try {
|
|
@@ -124,7 +124,7 @@ Read the oth://api-guide resource for full endpoint reference.`,
|
|
|
124
124
|
});
|
|
125
125
|
server.registerTool("oth_env", {
|
|
126
126
|
title: "OTH Environment",
|
|
127
|
-
description: "Switch OTH environment or show current state. Returns environment, base URL, and
|
|
127
|
+
description: "Switch OTH environment or show current state. Returns environment, base URL, whether a login has happened yet, and the username every call runs as (known before the first login). Calls are limited to that user's roles and patient groups.",
|
|
128
128
|
annotations: {
|
|
129
129
|
readOnlyHint: false,
|
|
130
130
|
destructiveHint: false,
|
|
@@ -145,10 +145,8 @@ Read the oth://api-guide resource for full endpoint reference.`,
|
|
|
145
145
|
environment: auth.environment,
|
|
146
146
|
baseUrl: auth.baseUrl,
|
|
147
147
|
authenticated: auth.isAuthenticated,
|
|
148
|
+
user: { username: await auth.configuredUsername() },
|
|
148
149
|
};
|
|
149
|
-
if (auth.isAuthenticated) {
|
|
150
|
-
info.user = auth.userInfo;
|
|
151
|
-
}
|
|
152
150
|
return {
|
|
153
151
|
content: [
|
|
154
152
|
{ type: "text", text: JSON.stringify(info, null, 2) },
|
package/package.json
CHANGED