@uphealth/mcp-server 1.0.0 → 1.1.0
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/DISTRIBUTION.md +11 -10
- package/README.md +3 -3
- package/dist/auth.js +36 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.js +1 -1
- package/dist/generated/signal-catalog.js +243 -0
- package/dist/generated/signal-catalog.js.map +1 -0
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/output-schemas.js +55 -0
- package/dist/output-schemas.js.map +1 -1
- package/dist/prompts.js +90 -0
- package/dist/prompts.js.map +1 -0
- package/dist/rails-client.js +30 -0
- package/dist/rails-client.js.map +1 -1
- package/dist/resources.js +127 -0
- package/dist/resources.js.map +1 -0
- package/dist/server.js +16 -2
- package/dist/server.js.map +1 -1
- package/dist/tool-auth-policy.js +52 -0
- package/dist/tool-auth-policy.js.map +1 -0
- package/dist/tool-result.js +34 -3
- package/dist/tool-result.js.map +1 -1
- package/dist/tools/create-patient-stream.js +36 -8
- package/dist/tools/create-patient-stream.js.map +1 -1
- package/dist/tools/get-next-cued-message.js +7 -2
- package/dist/tools/get-next-cued-message.js.map +1 -1
- package/dist/tools/index.js +5 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-sandbox-topics.js +4 -1
- package/dist/tools/list-sandbox-topics.js.map +1 -1
- package/dist/tools/lookup-health-fact.js +66 -0
- package/dist/tools/lookup-health-fact.js.map +1 -0
- package/dist/tools/read-stream-state.js +4 -2
- package/dist/tools/read-stream-state.js.map +1 -1
- package/package.json +1 -1
package/dist/rails-client.js
CHANGED
|
@@ -18,8 +18,15 @@ import { config } from "./config.js";
|
|
|
18
18
|
export async function callRails(opts) {
|
|
19
19
|
const url = new URL(opts.path, config.railsApiBaseUrl).toString();
|
|
20
20
|
const headers = {
|
|
21
|
+
...(opts.headers ?? {}),
|
|
21
22
|
Authorization: `Bearer ${opts.bearer}`,
|
|
22
23
|
Accept: "application/json",
|
|
24
|
+
// NOW #5 (2026-06-14) — identify the MCP server as the integration surface so
|
|
25
|
+
// the activation report can split cues by raw HTTP vs SDK vs MCP. Rails reads
|
|
26
|
+
// X-Uphealth-Client (falling back to User-Agent) onto the cued event payload.
|
|
27
|
+
// See CuedStreams::IntegrationSurface. The SDKs send the analogous token.
|
|
28
|
+
"User-Agent": `uphealth-mcp-server/${config.serverVersion}`,
|
|
29
|
+
"X-Uphealth-Client": `uphealth-mcp-server/${config.serverVersion}`,
|
|
23
30
|
};
|
|
24
31
|
const fetchInit = {
|
|
25
32
|
method: opts.method,
|
|
@@ -29,6 +36,29 @@ export async function callRails(opts) {
|
|
|
29
36
|
headers["Content-Type"] = "application/json";
|
|
30
37
|
fetchInit.body = JSON.stringify(opts.body);
|
|
31
38
|
}
|
|
39
|
+
return dispatch(url, fetchInit);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Perform an UNAUTHENTICATED GET to the Rails public API surface — Rung-0, the
|
|
43
|
+
* no-auth /v1/public/* lookup (2026-06-15). Sends NO Authorization header: the
|
|
44
|
+
* endpoint bypasses RequireTenant entirely, so there is no token to forward.
|
|
45
|
+
* Still carries the client-attribution tokens so server-side [public-lookup]
|
|
46
|
+
* traffic stays recognizable.
|
|
47
|
+
*/
|
|
48
|
+
export async function callRailsPublic(opts) {
|
|
49
|
+
const url = new URL(opts.path, config.railsApiBaseUrl).toString();
|
|
50
|
+
const headers = {
|
|
51
|
+
...(opts.headers ?? {}),
|
|
52
|
+
Accept: "application/json",
|
|
53
|
+
"User-Agent": `uphealth-mcp-server/${config.serverVersion}`,
|
|
54
|
+
"X-Uphealth-Client": `uphealth-mcp-server/${config.serverVersion}`,
|
|
55
|
+
};
|
|
56
|
+
return dispatch(url, { method: opts.method, headers });
|
|
57
|
+
}
|
|
58
|
+
// Shared fetch + JSON-parse for both the authenticated (callRails) and the
|
|
59
|
+
// unauthenticated (callRailsPublic) paths. Returns the full Rails response
|
|
60
|
+
// (status + parsed body + lowercased headers) so callers can branch on codes.
|
|
61
|
+
async function dispatch(url, fetchInit) {
|
|
32
62
|
const response = await fetch(url, fetchInit);
|
|
33
63
|
const responseHeaders = {};
|
|
34
64
|
response.headers.forEach((value, key) => {
|
package/dist/rails-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rails-client.js","sourceRoot":"","sources":["../src/rails-client.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,EAAE;AACF,oEAAoE;AACpE,mEAAmE;AACnE,+DAA+D;AAC/D,iEAAiE;AACjE,sEAAsE;AACtE,EAAE;AACF,qEAAqE;AAErE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAgBrC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAc,
|
|
1
|
+
{"version":3,"file":"rails-client.js","sourceRoot":"","sources":["../src/rails-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// A5 dev-surface (2026-06-15) — MCP resources.
|
|
2
|
+
//
|
|
3
|
+
// Two read-only resources:
|
|
4
|
+
// - `signal://catalog` — the live Signal template catalog (from the GENERATED,
|
|
5
|
+
// drift-guarded src/generated/signal-catalog.ts) PLUS the accepted cue
|
|
6
|
+
// `response_action` vocabulary (single-sourced from cue-actions.ts).
|
|
7
|
+
// - `signal://openapi` — the machine-readable OpenAPI 3.1 contract URLs +
|
|
8
|
+
// the receptivity-score semantics (strategy §4 #5 — "lets the server
|
|
9
|
+
// self-describe; cheap, read-only"). Points an agent at the spec to generate
|
|
10
|
+
// a client; carries no spec body (no runtime fetch, no drift).
|
|
11
|
+
//
|
|
12
|
+
// WHY: an agent reads `signal://catalog` BEFORE create_patient_stream so it can
|
|
13
|
+
// pick a template and pre-satisfy that template's audience-tag + patient-context
|
|
14
|
+
// gating — turning a clinical-template 422 into a 201 + first cue.
|
|
15
|
+
//
|
|
16
|
+
// BOUNDARY (locked, A1 §6.6 + dev-PMF strategy §4): read-only, metadata-only,
|
|
17
|
+
// non-PHI, Display-mode. The catalog carries the GATING CONTRACT only — never
|
|
18
|
+
// corpus message bodies (those are returned per-cue at runtime, after a stream
|
|
19
|
+
// exists) and never patient data. The resource handlers make NO Rails call.
|
|
20
|
+
import { SIGNAL_CATALOG } from "./generated/signal-catalog.js";
|
|
21
|
+
import { CUE_RESPONSE_ACTIONS } from "./cue-actions.js";
|
|
22
|
+
export const SIGNAL_CATALOG_URI = "signal://catalog";
|
|
23
|
+
export const SIGNAL_OPENAPI_URI = "signal://openapi";
|
|
24
|
+
// Stable public URLs for the machine-readable contract (same as the docs
|
|
25
|
+
// surfaces single-source in UphealthUs::DocsMarkdown). The spec is a static file
|
|
26
|
+
// served on uphealth.us; these are not the API base URL.
|
|
27
|
+
const OPENAPI_YAML_URL = "https://uphealth.us/openapi/signal-v1.yaml";
|
|
28
|
+
const OPENAPI_JSON_URL = "https://uphealth.us/openapi/signal-v1.json";
|
|
29
|
+
const POSTMAN_URL = "https://uphealth.us/openapi/uphealth-signal.postman_collection.json";
|
|
30
|
+
const DOCS_URL = "https://uphealth.us/docs";
|
|
31
|
+
// = AdaptiveResponse::Archetypes::CUE_FALLBACK_ACTION (Rails folds an
|
|
32
|
+
// unrecognized-but-well-formed response_action to this acknowledge-class value).
|
|
33
|
+
const CUE_FALLBACK_ACTION = "acknowledge";
|
|
34
|
+
// The resource body, assembled once at module load (static — no I/O).
|
|
35
|
+
const catalogBody = {
|
|
36
|
+
...SIGNAL_CATALOG,
|
|
37
|
+
cue_actions: {
|
|
38
|
+
accepted: [...CUE_RESPONSE_ACTIONS],
|
|
39
|
+
fallback_action: CUE_FALLBACK_ACTION,
|
|
40
|
+
forward_compat: "The cue vocabulary grows over time. Submit an accepted value, or any " +
|
|
41
|
+
"well-formed token matching /^[a-z][a-z0-9_]{0,63}$/ — unknown well-formed " +
|
|
42
|
+
"tokens are accepted (200) and echoed back as normalized_action='acknowledge'; " +
|
|
43
|
+
"a malformed token returns 422 (invalid_feedback). Treat unknown response_action " +
|
|
44
|
+
"values in responses as acknowledge-class.",
|
|
45
|
+
},
|
|
46
|
+
gating: {
|
|
47
|
+
to_create: "audience_tags MUST include every required_audience_tags and MUST NOT include " +
|
|
48
|
+
"any excluded_audience_tags, or create returns 422 (missing_required_audience_tags " +
|
|
49
|
+
"/ incompatible_audience_tag) on a production-tier key. A free sandbox (Discovery) " +
|
|
50
|
+
"key skips the gate and always cues the curated sandbox corpus — see list_sandbox_topics.",
|
|
51
|
+
patient_context: "Populate required_patient_context with de-identified values before create; a " +
|
|
52
|
+
"production create missing a required field is rejected. Never send PHI — Display " +
|
|
53
|
+
"mode renders in your own app, so no PHI and no BAA are involved.",
|
|
54
|
+
b_plus: "Where b_plus_gated_tag is set, Lists and Tips outside the template's primary " +
|
|
55
|
+
"category additionally require that tag on the patient; Facts are universal.",
|
|
56
|
+
},
|
|
57
|
+
usage: "Pick a template by clinical context, compute audience_tags from required/optional " +
|
|
58
|
+
"(never an excluded tag), fill required_patient_context with de-identified values, " +
|
|
59
|
+
"then call create_patient_stream — gating is pre-satisfied, so you get a 201 + first " +
|
|
60
|
+
"cue instead of a 422. Every cue is federally-sourced and audience-safety-checked. " +
|
|
61
|
+
"Not medical advice.",
|
|
62
|
+
};
|
|
63
|
+
// The OpenAPI / receptivity reference body (static — no I/O, no spec body, so it
|
|
64
|
+
// never drifts from or hammers the served spec). Points the agent at the
|
|
65
|
+
// machine-readable contract and explains the advisory receptivity score.
|
|
66
|
+
const openapiBody = {
|
|
67
|
+
description: "The machine-readable OpenAPI 3.1 contract for the Signal cued-stream API. " +
|
|
68
|
+
"Fetch one of the spec URLs below to self-describe the API or generate a client.",
|
|
69
|
+
openapi: {
|
|
70
|
+
yaml_url: OPENAPI_YAML_URL,
|
|
71
|
+
json_url: OPENAPI_JSON_URL,
|
|
72
|
+
postman_url: POSTMAN_URL,
|
|
73
|
+
docs_url: DOCS_URL,
|
|
74
|
+
},
|
|
75
|
+
receptivity: {
|
|
76
|
+
range: "0.0–1.0",
|
|
77
|
+
neutral: 0.5,
|
|
78
|
+
present_on: "every cue object (first_cue / next_cue / current_cue)",
|
|
79
|
+
meaning: "An advisory estimate of how receptive the patient is right now, built from " +
|
|
80
|
+
"their own feedback on prior cues; 0.50 is the neutral state of a brand-new " +
|
|
81
|
+
"patient. It is yours to read or ignore — the engine already applies it to " +
|
|
82
|
+
"choose the message.",
|
|
83
|
+
},
|
|
84
|
+
note: "Read-only reference. Not medical advice.",
|
|
85
|
+
};
|
|
86
|
+
// ListResources payload.
|
|
87
|
+
export const resources = [
|
|
88
|
+
{
|
|
89
|
+
uri: SIGNAL_CATALOG_URI,
|
|
90
|
+
name: "Signal template catalog & cue vocabulary",
|
|
91
|
+
description: "The live Signal stream templates (id, audience-tag + de-identified patient-context " +
|
|
92
|
+
"gating, duration, delivery modes, crisis-footer policy) plus the accepted cue " +
|
|
93
|
+
"response_action vocabulary. Read this BEFORE create_patient_stream to pick a template " +
|
|
94
|
+
"and pre-satisfy its gating. Metadata only — no patient data, no message bodies.",
|
|
95
|
+
mimeType: "application/json",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
uri: SIGNAL_OPENAPI_URI,
|
|
99
|
+
name: "OpenAPI specification & receptivity reference",
|
|
100
|
+
description: "The machine-readable OpenAPI 3.1 contract URLs (YAML / JSON / Postman) + the docs " +
|
|
101
|
+
"home, plus the advisory receptivity-score semantics every cue carries. Read this to " +
|
|
102
|
+
"self-describe the API or generate a client.",
|
|
103
|
+
mimeType: "application/json",
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
// ReadResource handler. Throws on an unknown uri (the SDK maps the throw to a
|
|
107
|
+
// JSON-RPC error). Both resources are static JSON, assembled at module load.
|
|
108
|
+
const RESOURCE_BODIES = {
|
|
109
|
+
[SIGNAL_CATALOG_URI]: catalogBody,
|
|
110
|
+
[SIGNAL_OPENAPI_URI]: openapiBody,
|
|
111
|
+
};
|
|
112
|
+
export function readResource(uri) {
|
|
113
|
+
const body = RESOURCE_BODIES[uri];
|
|
114
|
+
if (body === undefined) {
|
|
115
|
+
throw new Error(`Unknown resource: ${uri}. Available: ${SIGNAL_CATALOG_URI}, ${SIGNAL_OPENAPI_URI}.`);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
contents: [
|
|
119
|
+
{
|
|
120
|
+
uri,
|
|
121
|
+
mimeType: "application/json",
|
|
122
|
+
text: JSON.stringify(body, null, 2),
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -8,12 +8,18 @@
|
|
|
8
8
|
//
|
|
9
9
|
// Spec: docs/spec_track_a_a4_mcp_server.md §3 + §4 + §6.
|
|
10
10
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
11
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
12
12
|
import { config } from "./config.js";
|
|
13
13
|
import { tools, findToolByName } from "./tools/index.js";
|
|
14
14
|
import { errorResult } from "./tool-result.js";
|
|
15
|
+
import { resources, readResource } from "./resources.js";
|
|
16
|
+
import { prompts, getPrompt } from "./prompts.js";
|
|
15
17
|
export function buildServer() {
|
|
16
|
-
const server = new Server({ name: config.serverName, version: config.serverVersion },
|
|
18
|
+
const server = new Server({ name: config.serverName, version: config.serverVersion },
|
|
19
|
+
// A5 dev-surface (2026-06-15) — resources (the signal://catalog template +
|
|
20
|
+
// cue-vocabulary metadata) and prompts (pick_template / evaluate_signal) join
|
|
21
|
+
// tools so an agent can pick a template + pre-satisfy gating before creating.
|
|
22
|
+
{ capabilities: { tools: {}, resources: {}, prompts: {} } });
|
|
17
23
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
18
24
|
tools: tools.map((t) => ({
|
|
19
25
|
name: t.name,
|
|
@@ -24,6 +30,14 @@ export function buildServer() {
|
|
|
24
30
|
outputSchema: t.outputSchema,
|
|
25
31
|
})),
|
|
26
32
|
}));
|
|
33
|
+
// A5 dev-surface — resources: read-only, metadata-only signal://catalog.
|
|
34
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
35
|
+
resources: resources.map((r) => ({ ...r })),
|
|
36
|
+
}));
|
|
37
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (req) => readResource(req.params.uri));
|
|
38
|
+
// A5 dev-surface — prompts: the canonical pick-template / run-an-episode flows.
|
|
39
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts }));
|
|
40
|
+
server.setRequestHandler(GetPromptRequestSchema, async (req) => getPrompt(req.params.name, req.params.arguments ?? {}));
|
|
27
41
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
28
42
|
const toolName = req.params.name;
|
|
29
43
|
const args = (req.params.arguments ?? {});
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,kEAAkE;AAClE,4EAA4E;AAC5E,EAAE;AACF,8EAA8E;AAC9E,2EAA2E;AAC3E,gFAAgF;AAChF,EAAE;AACF,yDAAyD;AAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GAEvB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Per-tool auth policy for the Streamable HTTP transport (Rung-0, 2026-06-15).
|
|
2
|
+
//
|
|
3
|
+
// Extracted from index.ts as a pure, side-effect-free module — like auth.ts — so
|
|
4
|
+
// it can be unit-tested without booting the Express listener (index.ts calls
|
|
5
|
+
// startHttpServer() at import time).
|
|
6
|
+
//
|
|
7
|
+
// The guard used to be all-or-nothing: NO Bearer → 401 for EVERY /mcp request,
|
|
8
|
+
// including the handshake. Rung-0 relaxes it to a per-tool policy so the one
|
|
9
|
+
// keyless tool (lookup_health_fact) — and ONLY the JSON-RPC methods needed to
|
|
10
|
+
// reach it — work without a key, while every keyed tool still 401s. Rails stays
|
|
11
|
+
// the auth authority (defense in depth): even if this gate were bypassed, a keyed
|
|
12
|
+
// tool forwards an empty Bearer and Rails 401s it.
|
|
13
|
+
// The ONLY tool callable without a Bearer. Add a tool here = make it keyless.
|
|
14
|
+
export const NO_AUTH_TOOLS = new Set(["lookup_health_fact"]);
|
|
15
|
+
// MCP handshake / discovery methods that must work keyless so a client can
|
|
16
|
+
// initialize a session and discover lookup_health_fact at all. These expose only
|
|
17
|
+
// public catalog metadata (tool/resource/prompt names + schemas) — no secrets,
|
|
18
|
+
// no tenant data. Notably ABSENT: resources/read + prompts/get + every keyed
|
|
19
|
+
// tools/call, which still require a Bearer.
|
|
20
|
+
export const NO_AUTH_METHODS = new Set([
|
|
21
|
+
"initialize",
|
|
22
|
+
"notifications/initialized",
|
|
23
|
+
"ping",
|
|
24
|
+
"tools/list",
|
|
25
|
+
"resources/list",
|
|
26
|
+
"prompts/list",
|
|
27
|
+
]);
|
|
28
|
+
export function messageAllowedWithoutBearer(msg) {
|
|
29
|
+
if (!msg || typeof msg !== "object")
|
|
30
|
+
return false;
|
|
31
|
+
const method = msg.method;
|
|
32
|
+
if (typeof method !== "string")
|
|
33
|
+
return false;
|
|
34
|
+
if (NO_AUTH_METHODS.has(method))
|
|
35
|
+
return true;
|
|
36
|
+
if (method === "tools/call") {
|
|
37
|
+
const name = msg.params?.name;
|
|
38
|
+
return typeof name === "string" && NO_AUTH_TOOLS.has(name);
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// A request (single message OR a JSON-RPC batch) may proceed without a Bearer
|
|
43
|
+
// ONLY if EVERY message in it is individually allowed — a batch that mixes a
|
|
44
|
+
// keyless method with a keyed tools/call is rejected wholesale. An empty/garbage
|
|
45
|
+
// body is not allowed (falls through to the 401 onboarding hint).
|
|
46
|
+
export function requestAllowedWithoutBearer(body) {
|
|
47
|
+
const messages = Array.isArray(body) ? body : [body];
|
|
48
|
+
if (messages.length === 0)
|
|
49
|
+
return false;
|
|
50
|
+
return messages.every(messageAllowedWithoutBearer);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=tool-auth-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-auth-policy.js","sourceRoot":"","sources":["../src/tool-auth-policy.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,iFAAiF;AACjF,6EAA6E;AAC7E,qCAAqC;AACrC,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,8EAA8E;AAC9E,gFAAgF;AAChF,kFAAkF;AAClF,mDAAmD;AAEnD,8EAA8E;AAC9E,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAS,CAAC,oBAAoB,CAAC,CAAC,CAAC;AAErE,2EAA2E;AAC3E,iFAAiF;AACjF,+EAA+E;AAC/E,6EAA6E;AAC7E,4CAA4C;AAC5C,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,GAAG,CAAS;IAC7C,YAAY;IACZ,2BAA2B;IAC3B,MAAM;IACN,YAAY;IACZ,gBAAgB;IAChB,cAAc;CACf,CAAC,CAAC;AAEH,MAAM,UAAU,2BAA2B,CAAC,GAAY;IACtD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAClD,MAAM,MAAM,GAAI,GAA4B,CAAC,MAAM,CAAC;IACpD,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC7C,IAAI,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAI,GAAuC,CAAC,MAAM,EAAE,IAAI,CAAC;QACnE,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,6EAA6E;AAC7E,iFAAiF;AACjF,kEAAkE;AAClE,MAAM,UAAU,2BAA2B,CAAC,IAAa;IACvD,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACrD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,QAAQ,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;AACrD,CAAC"}
|
package/dist/tool-result.js
CHANGED
|
@@ -4,13 +4,44 @@
|
|
|
4
4
|
// responses propagate as isError with a structured message. Successful
|
|
5
5
|
// responses include the Rails body verbatim (preserving the _meta
|
|
6
6
|
// block per A1 §6.7).
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Pull the RateLimit-Limit/Remaining/Reset triple out of a Rails response's
|
|
9
|
+
* headers. `fetch` lowercases header names, so we read the lowercase keys.
|
|
10
|
+
* Returns undefined when none are present — Rails omits them on non-Discovery
|
|
11
|
+
* tiers (no cue cap), so the field is absent rather than zero-filled there.
|
|
12
|
+
*/
|
|
13
|
+
export function extractRateLimit(headers) {
|
|
14
|
+
if (!headers)
|
|
15
|
+
return undefined;
|
|
16
|
+
const limit = headers["ratelimit-limit"];
|
|
17
|
+
const remaining = headers["ratelimit-remaining"];
|
|
18
|
+
const reset = headers["ratelimit-reset"];
|
|
19
|
+
if (limit == null && remaining == null && reset == null)
|
|
20
|
+
return undefined;
|
|
21
|
+
const rl = {};
|
|
22
|
+
if (limit != null)
|
|
23
|
+
rl.limit = Number(limit);
|
|
24
|
+
if (remaining != null)
|
|
25
|
+
rl.remaining = Number(remaining);
|
|
26
|
+
if (reset != null)
|
|
27
|
+
rl.reset = Number(reset);
|
|
28
|
+
return rl;
|
|
29
|
+
}
|
|
30
|
+
export function successResult(body, headers) {
|
|
31
|
+
// Merge an advisory `rate_limit` block into the payload when Rails sent the
|
|
32
|
+
// headers. Only when body is a plain object (every Signal 2xx envelope is) —
|
|
33
|
+
// otherwise pass it through untouched. When no headers are present the body is
|
|
34
|
+
// returned VERBATIM (identity preserved), matching the pre-A5-parity contract.
|
|
35
|
+
const rateLimit = extractRateLimit(headers);
|
|
36
|
+
const structured = rateLimit && body !== null && typeof body === "object" && !Array.isArray(body)
|
|
37
|
+
? { ...body, rate_limit: rateLimit }
|
|
38
|
+
: body;
|
|
8
39
|
return {
|
|
9
40
|
// The text block stays as the JSON serialization of structuredContent so
|
|
10
41
|
// hosts that don't support structuredContent still get the full payload
|
|
11
42
|
// (MCP backward-compat guidance).
|
|
12
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
13
|
-
structuredContent:
|
|
43
|
+
content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
|
|
44
|
+
structuredContent: structured,
|
|
14
45
|
};
|
|
15
46
|
}
|
|
16
47
|
export function errorResult(message) {
|
package/dist/tool-result.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-result.js","sourceRoot":"","sources":["../src/tool-result.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,gEAAgE;AAChE,uEAAuE;AACvE,kEAAkE;AAClE,sBAAsB;
|
|
1
|
+
{"version":3,"file":"tool-result.js","sourceRoot":"","sources":["../src/tool-result.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
// cued message. Each subsequent cue requires submitting the prior
|
|
7
7
|
// message's feedback (the moat per A1 §3).
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// template_id accepts any LIVE template (SIGNAL_TEMPLATE_IDS, derived from the
|
|
10
|
+
// drift-guarded generated catalog — the single source shared with the
|
|
11
|
+
// signal://catalog resource). Clinical templates require audience_tags +
|
|
12
|
+
// patient_context the agent should pre-satisfy from the catalog resource (else a
|
|
13
|
+
// production create 422s). delivery_mode is `display` in v1; Deliver unlocks at
|
|
14
|
+
// Per-Episode tier after BAA execution.
|
|
12
15
|
//
|
|
13
16
|
// Scope required: stream:create.
|
|
14
17
|
// Spec: docs/spec_track_a_a4_mcp_server.md §6.3.
|
|
@@ -16,9 +19,16 @@ import { callRails, isErrorStatus } from "../rails-client.js";
|
|
|
16
19
|
import { currentContext } from "../context.js";
|
|
17
20
|
import { successResult, railsErrorToToolResult, } from "../tool-result.js";
|
|
18
21
|
import { createPatientStreamOutputSchema } from "../output-schemas.js";
|
|
22
|
+
import { SIGNAL_TEMPLATE_IDS } from "../generated/signal-catalog.js";
|
|
19
23
|
export const createPatientStreamTool = {
|
|
20
24
|
name: "create_patient_stream",
|
|
21
|
-
description: "Create a cued stream for
|
|
25
|
+
description: "Create a Signal cued stream for one patient and get the first cue back synchronously. " +
|
|
26
|
+
"Every cue is federally-sourced, audience-safety-checked, and sequenced on the patient's " +
|
|
27
|
+
"prior response — so your agent never fabricates health content. Choose a template_id from " +
|
|
28
|
+
"the signal://catalog resource and pre-satisfy its required audience_tags + patient_context " +
|
|
29
|
+
"(clinical templates 422 without them). Display mode renders in your app: no PHI, no BAA. " +
|
|
30
|
+
"Each later cue MUST submit the prior message's feedback — the engine refuses to advance " +
|
|
31
|
+
"without it (the sequencing moat).",
|
|
22
32
|
annotations: {
|
|
23
33
|
title: "Create patient stream",
|
|
24
34
|
readOnlyHint: false,
|
|
@@ -30,8 +40,13 @@ export const createPatientStreamTool = {
|
|
|
30
40
|
properties: {
|
|
31
41
|
template_id: {
|
|
32
42
|
type: "string",
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
// Single-sourced from the drift-guarded generated catalog (same source as
|
|
44
|
+
// the signal://catalog resource) — never hand-list a second copy here.
|
|
45
|
+
enum: [...SIGNAL_TEMPLATE_IDS],
|
|
46
|
+
description: "One of the live Signal templates. Read the signal://catalog resource for each " +
|
|
47
|
+
"template's required audience_tags + patient_context. On a production key a clinical " +
|
|
48
|
+
"template 422s if its required tags/context are missing — pre-satisfy them from the " +
|
|
49
|
+
"catalog. A free sandbox (Discovery) key always cues the curated sandbox corpus.",
|
|
35
50
|
},
|
|
36
51
|
attributes: {
|
|
37
52
|
type: "object",
|
|
@@ -52,6 +67,11 @@ export const createPatientStreamTool = {
|
|
|
52
67
|
type: "integer",
|
|
53
68
|
default: 14,
|
|
54
69
|
},
|
|
70
|
+
idempotency_key: {
|
|
71
|
+
type: "string",
|
|
72
|
+
format: "uuid",
|
|
73
|
+
description: "Optional. Stripe-style create idempotency: a retried create with the same key returns the ORIGINAL stream instead of minting (and billing) a second one. Forwarded as the Idempotency-Key request header. UUID v4 recommended.",
|
|
74
|
+
},
|
|
55
75
|
},
|
|
56
76
|
required: ["template_id"],
|
|
57
77
|
additionalProperties: true,
|
|
@@ -59,16 +79,24 @@ export const createPatientStreamTool = {
|
|
|
59
79
|
outputSchema: createPatientStreamOutputSchema,
|
|
60
80
|
async handler(args) {
|
|
61
81
|
const { bearer } = currentContext();
|
|
82
|
+
// idempotency_key is a REQUEST HEADER on the Rails side (read at
|
|
83
|
+
// streams_controller#create), not a body param — strip it from the body and
|
|
84
|
+
// forward it as Idempotency-Key. Everything else passes through verbatim.
|
|
85
|
+
const { idempotency_key, ...streamArgs } = args;
|
|
86
|
+
const headers = typeof idempotency_key === "string" && idempotency_key.length > 0
|
|
87
|
+
? { "Idempotency-Key": idempotency_key }
|
|
88
|
+
: undefined;
|
|
62
89
|
const response = await callRails({
|
|
63
90
|
method: "POST",
|
|
64
91
|
path: "/v1/signal/streams",
|
|
65
92
|
bearer,
|
|
66
|
-
body:
|
|
93
|
+
body: streamArgs,
|
|
94
|
+
headers,
|
|
67
95
|
});
|
|
68
96
|
if (isErrorStatus(response.status)) {
|
|
69
97
|
return railsErrorToToolResult(response.status, response.body, "create_patient_stream", response.headers);
|
|
70
98
|
}
|
|
71
|
-
return successResult(response.body);
|
|
99
|
+
return successResult(response.body, response.headers);
|
|
72
100
|
},
|
|
73
101
|
};
|
|
74
102
|
//# sourceMappingURL=create-patient-stream.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-patient-stream.js","sourceRoot":"","sources":["../../src/tools/create-patient-stream.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,eAAe;AACf,4BAA4B;AAC5B,iEAAiE;AACjE,kEAAkE;AAClE,2CAA2C;AAC3C,EAAE;AACF,
|
|
1
|
+
{"version":3,"file":"create-patient-stream.js","sourceRoot":"","sources":["../../src/tools/create-patient-stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,7 +16,12 @@ import { CUE_RESPONSE_ACTIONS } from "../cue-actions.js";
|
|
|
16
16
|
import { getNextCuedMessageOutputSchema } from "../output-schemas.js";
|
|
17
17
|
export const getNextCuedMessageTool = {
|
|
18
18
|
name: "get_next_cued_message",
|
|
19
|
-
description: "Submit the
|
|
19
|
+
description: "Submit the patient's response to the prior cue and get the next one — federally-sourced, " +
|
|
20
|
+
"audience-safety-checked, and re-sequenced on that feedback. REFUSES WITHOUT PRIOR-MESSAGE " +
|
|
21
|
+
"FEEDBACK (the moat): the engine will not advance a stream blind, returning 409 if you skip " +
|
|
22
|
+
"it. response_action is one of the accepted cue vocabulary (see the signal://catalog " +
|
|
23
|
+
"resource). Same idempotency_key + same feedback returns the cached cue; conflicting " +
|
|
24
|
+
"feedback under the same key returns 409.",
|
|
20
25
|
annotations: {
|
|
21
26
|
title: "Submit feedback, get next cued message",
|
|
22
27
|
readOnlyHint: false,
|
|
@@ -72,7 +77,7 @@ export const getNextCuedMessageTool = {
|
|
|
72
77
|
if (isErrorStatus(response.status)) {
|
|
73
78
|
return railsErrorToToolResult(response.status, response.body, "get_next_cued_message", response.headers);
|
|
74
79
|
}
|
|
75
|
-
return successResult(response.body);
|
|
80
|
+
return successResult(response.body, response.headers);
|
|
76
81
|
},
|
|
77
82
|
};
|
|
78
83
|
//# sourceMappingURL=get-next-cued-message.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-next-cued-message.js","sourceRoot":"","sources":["../../src/tools/get-next-cued-message.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,eAAe;AACf,oCAAoC;AACpC,kEAAkE;AAClE,uEAAuE;AACvE,qEAAqE;AACrE,kEAAkE;AAClE,EAAE;AACF,8BAA8B;AAC9B,qEAAqE;AAErE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAuB,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,WAAW,GAEZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAC;AAEtE,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"get-next-cued-message.js","sourceRoot":"","sources":["../../src/tools/get-next-cued-message.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
// Track A A4 PR 2 — MCP tool registry.
|
|
2
|
-
//
|
|
2
|
+
// The 4 keyed v1 tools per docs/spec_track_a_a4_mcp_server.md §6.3, plus the
|
|
3
|
+
// Rung-0 no-auth lookup_health_fact (2026-06-15) — the one keyless tool (the
|
|
4
|
+
// per-tool guard relax lives in index.ts; the others still 401 without a key).
|
|
3
5
|
import { createPatientStreamTool } from "./create-patient-stream.js";
|
|
4
6
|
import { getNextCuedMessageTool } from "./get-next-cued-message.js";
|
|
5
7
|
import { readStreamStateTool } from "./read-stream-state.js";
|
|
6
8
|
import { listSandboxTopicsTool } from "./list-sandbox-topics.js";
|
|
9
|
+
import { lookupHealthFactTool } from "./lookup-health-fact.js";
|
|
7
10
|
export const tools = [
|
|
11
|
+
lookupHealthFactTool,
|
|
8
12
|
createPatientStreamTool,
|
|
9
13
|
getNextCuedMessageTool,
|
|
10
14
|
readStreamStateTool,
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAE/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAE/D,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,oBAAoB;IACpB,uBAAuB;IACvB,sBAAsB;IACtB,mBAAmB;IACnB,qBAAqB;CACb,CAAC;AAIX,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -14,7 +14,10 @@ import { successResult, railsErrorToToolResult, } from "../tool-result.js";
|
|
|
14
14
|
import { listSandboxTopicsOutputSchema } from "../output-schemas.js";
|
|
15
15
|
export const listSandboxTopicsTool = {
|
|
16
16
|
name: "list_sandbox_topics",
|
|
17
|
-
description: "
|
|
17
|
+
description: "List the curated sandbox topic labels that define the free Discovery corpus — every topic " +
|
|
18
|
+
"federally-sourced and audience-safety-checked. Use it to preview what content domains exist " +
|
|
19
|
+
"before creating a stream. Does NOT enumerate messages — the corpus stays internal. For the " +
|
|
20
|
+
"full live template catalog + gating contract, read the signal://catalog resource.",
|
|
18
21
|
annotations: {
|
|
19
22
|
title: "List sandbox topics",
|
|
20
23
|
readOnlyHint: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-sandbox-topics.js","sourceRoot":"","sources":["../../src/tools/list-sandbox-topics.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,EAAE;AACF,8CAA8C;AAC9C,kCAAkC;AAClC,+DAA+D;AAC/D,oEAAoE;AACpE,kDAAkD;AAClD,EAAE;AACF,qEAAqE;AACrE,iDAAiD;AAEjD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAuB,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EACL,aAAa,EACb,sBAAsB,GAEvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAC;AAErE,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"list-sandbox-topics.js","sourceRoot":"","sources":["../../src/tools/list-sandbox-topics.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,EAAE;AACF,8CAA8C;AAC9C,kCAAkC;AAClC,+DAA+D;AAC/D,oEAAoE;AACpE,kDAAkD;AAClD,EAAE;AACF,qEAAqE;AACrE,iDAAiD;AAEjD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAuB,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EACL,aAAa,EACb,sBAAsB,GAEvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAC;AAErE,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EACT,4FAA4F;QAC5F,8FAA8F;QAC9F,6FAA6F;QAC7F,mFAAmF;IACrF,WAAW,EAAE;QACX,KAAK,EAAE,qBAAqB;QAC5B,YAAY,EAAE,IAAI;QAClB,aAAa,EAAE,KAAK;KACrB;IACD,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE,EAAE;QACd,oBAAoB,EAAE,KAAK;KAC5B;IACD,YAAY,EAAE,6BAA6B;IAC3C,KAAK,CAAC,OAAO,CAAC,KAA8B;QAC1C,MAAM,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC;YAC/B,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,2BAA2B;YACjC,MAAM;SACP,CAAC,CAAC;QAEH,IAAI,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACnC,OAAO,sBAAsB,CAC3B,QAAQ,CAAC,MAAM,EACf,QAAQ,CAAC,IAA6B,EACtC,qBAAqB,EACrB,QAAQ,CAAC,OAAO,CACjB,CAAC;QACJ,CAAC;QACD,OAAO,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Rung-0 (2026-06-15) — the no-auth public health-fact lookup tool.
|
|
2
|
+
//
|
|
3
|
+
// Wraps the keyless Rails endpoint
|
|
4
|
+
// GET /v1/public/health-fact?q=<health question or topic>
|
|
5
|
+
// (Api::V1::Public::HealthFactsController). This is the ONLY tool that needs no
|
|
6
|
+
// Bearer key — the index.ts guard relaxes the all-or-nothing 401 to a per-tool
|
|
7
|
+
// policy so an agent can call Uphealth the first time it needs a health fact with
|
|
8
|
+
// ZERO setup. The existing keyed tools still 401 without a key.
|
|
9
|
+
//
|
|
10
|
+
// It returns ONE already-public federal fact (the exact single sample already on
|
|
11
|
+
// uphealth.me/health/<slug>) + its source + an upgrade CTA. NO numeric scores /
|
|
12
|
+
// model outputs — those are the paid intelligence behind a key.
|
|
13
|
+
//
|
|
14
|
+
// Spec: docs/spec_signal_plg_growth_loop_2026-06-14.md §6 (C5).
|
|
15
|
+
import { callRailsPublic, isErrorStatus } from "../rails-client.js";
|
|
16
|
+
import { successResult, errorResult } from "../tool-result.js";
|
|
17
|
+
import { lookupHealthFactOutputSchema } from "../output-schemas.js";
|
|
18
|
+
export const lookupHealthFactTool = {
|
|
19
|
+
name: "lookup_health_fact",
|
|
20
|
+
description: "Zero setup — NO key required. Returns ONE already-public federal health fact for a question or topic " +
|
|
21
|
+
'(e.g. "high blood pressure", "sleep", "type 2 diabetes"), with its federal source (CDC / MedlinePlus / ' +
|
|
22
|
+
"NIH) and a link to the public uphealth.me page. Free + rate-limited. For adaptive, patient-specific " +
|
|
23
|
+
"message sequencing — receptivity-timed cues + audience-safety verdicts over the full library — get a " +
|
|
24
|
+
"free Discovery key at https://uphealth.us/signup.",
|
|
25
|
+
annotations: {
|
|
26
|
+
title: "Look up a public health fact (no key)",
|
|
27
|
+
readOnlyHint: true,
|
|
28
|
+
openWorldHint: false,
|
|
29
|
+
},
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
q: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: 'A health question or topic, e.g. "high blood pressure", "sleep", or "type 2 diabetes".',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
required: ["q"],
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
},
|
|
41
|
+
outputSchema: lookupHealthFactOutputSchema,
|
|
42
|
+
async handler(args) {
|
|
43
|
+
const q = typeof args.q === "string" ? args.q : "";
|
|
44
|
+
// No bearer — the public endpoint bypasses RequireTenant. q is URL-encoded.
|
|
45
|
+
const response = await callRailsPublic({
|
|
46
|
+
method: "GET",
|
|
47
|
+
path: `/v1/public/health-fact?q=${encodeURIComponent(q)}`,
|
|
48
|
+
});
|
|
49
|
+
if (isErrorStatus(response.status)) {
|
|
50
|
+
const body = response.body;
|
|
51
|
+
// Rails / rack-attack already wrote agent-friendly copy:
|
|
52
|
+
// 404 not_found → "No public federal fact matched … get a free key …"
|
|
53
|
+
// 429 rate_limited → "Public lookup rate limit reached. Get a free … key …" (+ signup_url)
|
|
54
|
+
// 422 invalid_params / 503 content_unavailable → their messages
|
|
55
|
+
const message = (typeof body?.message === "string" && body.message) ||
|
|
56
|
+
(typeof body?.error === "string" && body.error) ||
|
|
57
|
+
`Unexpected response (status ${response.status}).`;
|
|
58
|
+
const signup = typeof body?.signup_url === "string" ? ` ${body.signup_url}` : "";
|
|
59
|
+
return errorResult(`lookup_health_fact: ${message}${signup}`);
|
|
60
|
+
}
|
|
61
|
+
// 200 — the fact, verbatim (no rate_limit headers on this surface, so the
|
|
62
|
+
// body passes through unchanged and matches lookupHealthFactOutputSchema).
|
|
63
|
+
return successResult(response.body);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=lookup-health-fact.js.map
|