@uphealth/mcp-server 1.0.0 → 1.1.1

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.
Files changed (40) hide show
  1. package/DISTRIBUTION.md +11 -10
  2. package/README.md +3 -3
  3. package/dist/auth.js +36 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/config.js +1 -1
  6. package/dist/generated/signal-catalog.js +243 -0
  7. package/dist/generated/signal-catalog.js.map +1 -0
  8. package/dist/index.js +15 -7
  9. package/dist/index.js.map +1 -1
  10. package/dist/output-schemas.js +55 -0
  11. package/dist/output-schemas.js.map +1 -1
  12. package/dist/prompts.js +90 -0
  13. package/dist/prompts.js.map +1 -0
  14. package/dist/rails-client.js +30 -0
  15. package/dist/rails-client.js.map +1 -1
  16. package/dist/resources.js +127 -0
  17. package/dist/resources.js.map +1 -0
  18. package/dist/server.js +16 -2
  19. package/dist/server.js.map +1 -1
  20. package/dist/stdio-context.js +19 -0
  21. package/dist/stdio-context.js.map +1 -0
  22. package/dist/stdio.js +23 -14
  23. package/dist/stdio.js.map +1 -1
  24. package/dist/tool-auth-policy.js +52 -0
  25. package/dist/tool-auth-policy.js.map +1 -0
  26. package/dist/tool-result.js +34 -3
  27. package/dist/tool-result.js.map +1 -1
  28. package/dist/tools/create-patient-stream.js +36 -8
  29. package/dist/tools/create-patient-stream.js.map +1 -1
  30. package/dist/tools/get-next-cued-message.js +7 -2
  31. package/dist/tools/get-next-cued-message.js.map +1 -1
  32. package/dist/tools/index.js +5 -1
  33. package/dist/tools/index.js.map +1 -1
  34. package/dist/tools/list-sandbox-topics.js +4 -1
  35. package/dist/tools/list-sandbox-topics.js.map +1 -1
  36. package/dist/tools/lookup-health-fact.js +66 -0
  37. package/dist/tools/lookup-health-fact.js.map +1 -0
  38. package/dist/tools/read-stream-state.js +4 -2
  39. package/dist/tools/read-stream-state.js.map +1 -1
  40. package/package.json +1 -1
@@ -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) => {
@@ -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,IAK5C;IACC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC,QAAQ,EAAE,CAAC;IAClE,MAAM,OAAO,GAA2B;QACtC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;QACtC,MAAM,EAAE,kBAAkB;KAC3B,CAAC;IAEF,MAAM,SAAS,GAAgB;QAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO;KACR,CAAC;IAEF,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;QAC7C,SAAS,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAE7C,MAAM,eAAe,GAA2B,EAAE,CAAC;IACnD,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACtC,eAAe,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,IAAI,MAAM,GAAY,IAAI,CAAC;IAC3B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;YACnE,iEAAiE;YACjE,MAAM,GAAG,EAAE,KAAK,EAAE,mBAAmB,EAAE,GAAG,EAAE,IAAI,EAA2B,CAAC;QAC9E,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,MAAW,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC;AAClF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,MAAM,GAAG,GAAG,IAAI,MAAM,IAAI,GAAG,CAAC;AACvC,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 }, { capabilities: { tools: {} } });
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 ?? {});
@@ -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;AAE/C,MAAM,UAAU,WAAW;IACzB,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,aAAa,EAAE,EAC1D,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;IAEF,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACvB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,8EAA8E;YAC9E,YAAY,EAAE,CAAC,CAAC,YAAY;SAC7B,CAAC,CAAC;KACJ,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,GAAG,EAA2B,EAAE;QACrF,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;QACjC,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAA4B,CAAC;QACrE,MAAM,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,WAAW,CAAC,iBAAiB,QAAQ,EAAE,CAAmB,CAAC;QACpE,CAAC;QACD,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAmB,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ // Pure resolver for the stdio entrypoint's process-level fallback context.
2
+ //
3
+ // Split into its own side-effect-free module (only a type-only import) so it can
4
+ // be unit-tested WITHOUT importing stdio.ts — whose bottom-of-file main() boots a
5
+ // transport on import. (An `import.meta.url === argv[1]` entrypoint guard was
6
+ // considered and rejected: npm runs the published bin via a `.bin/…` symlink, so
7
+ // argv[1] is the symlink path while import.meta.url is the real dist/stdio.js —
8
+ // they don't match, and main() would never run. Keeping the helper separate lets
9
+ // stdio.ts call main() unconditionally, as the entrypoint must.)
10
+ //
11
+ // Contract: a missing / whitespace-only key yields an EMPTY bearer (keyless
12
+ // mode) — NOT a process exit. With an empty bearer, lookup_health_fact still
13
+ // works (it calls the public endpoint with no bearer) and every keyed tool
14
+ // forwards the empty bearer so Rails 401s it at call-time. Rails is the auth
15
+ // authority. A present key is returned trimmed.
16
+ export function stdioFallbackContext(rawKey) {
17
+ return { bearer: (rawKey ?? "").trim() };
18
+ }
19
+ //# sourceMappingURL=stdio-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio-context.js","sourceRoot":"","sources":["../src/stdio-context.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,EAAE;AACF,iFAAiF;AACjF,kFAAkF;AAClF,8EAA8E;AAC9E,iFAAiF;AACjF,gFAAgF;AAChF,iFAAiF;AACjF,iEAAiE;AACjE,EAAE;AACF,4EAA4E;AAC5E,6EAA6E;AAC7E,2EAA2E;AAC3E,6EAA6E;AAC7E,gDAAgD;AAIhD,MAAM,UAAU,oBAAoB,CAAC,MAA0B;IAC7D,OAAO,EAAE,MAAM,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;AAC3C,CAAC"}
package/dist/stdio.js CHANGED
@@ -4,30 +4,39 @@
4
4
  //
5
5
  // npx -y @uphealth/mcp-server
6
6
  //
7
- // with the user's key in the environment:
7
+ // KEYLESS by default. The one keyless tool (lookup_health_fact) works with NO
8
+ // key, matching the hosted Streamable HTTP deployment at mcp.uphealth.us
9
+ // (src/index.ts) — so "zero setup" is true on BOTH the npx and the remote path,
10
+ // not just the remote. Supply a free sandbox key in the environment to unlock the
11
+ // keyed tools (create_patient_stream, get_next_cued_message, …):
8
12
  //
9
13
  // UPHEALTH_API_KEY=up_sandbox_…
10
14
  //
11
- // This is the dominant MCP install pattern (local stdio), complementing the
12
- // hosted Streamable HTTP deployment at mcp.uphealth.us (src/index.ts). The two
13
- // share the same tools and the same Server factory (server.ts).
15
+ // The two transports share the same tools and the same Server factory (server.ts).
14
16
  //
15
- // A stdio process is single-tenant: one user, one key for its lifetime. We set
16
- // that key as the process-level fallback context, so the same tool handlers
17
- // that read the per-request bearer over HTTP work unchanged here.
17
+ // A stdio process is single-tenant: one user, one bearer for its lifetime. We set
18
+ // that bearer as the process-level fallback context, so the same tool handlers
19
+ // that read the per-request bearer over HTTP work unchanged here. With NO key we
20
+ // set an EMPTY bearer: lookup_health_fact hits the public endpoint (no bearer
21
+ // needed) and works; every keyed tool forwards the empty bearer and Rails 401s it
22
+ // at call-time. Rails stays the single auth authority (defense in depth) — we do
23
+ // not pre-reject keyless here; we let the keyless tool through and let Rails gate
24
+ // the keyed ones, exactly as the HTTP per-tool policy does (tool-auth-policy.ts).
18
25
  import "./bootstrap-env.js"; // must run before anything imports config.ts
19
26
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20
27
  import { setFallbackContext } from "./context.js";
28
+ import { stdioFallbackContext } from "./stdio-context.js";
21
29
  import { buildServer } from "./server.js";
22
30
  async function main() {
23
- const apiKey = process.env.UPHEALTH_API_KEY?.trim();
24
- if (!apiKey) {
25
- // Write guidance to stderr (stdout is the MCP JSON-RPC channel).
26
- console.error("Uphealth MCP: missing UPHEALTH_API_KEY. Get a free sandbox key at " +
27
- "https://uphealth.us/signup, then set UPHEALTH_API_KEY in your MCP client config.");
28
- process.exit(1);
31
+ const ctx = stdioFallbackContext(process.env.UPHEALTH_API_KEY);
32
+ if (!ctx.bearer) {
33
+ // Informational, NOT fatal. stdout is the MCP JSON-RPC channel, so guidance
34
+ // goes to stderr. Keyless mode is fully functional for lookup_health_fact.
35
+ console.error("Uphealth MCP: running keyless lookup_health_fact works with no key. " +
36
+ "For the keyed tools (patient streams + cues), get a free sandbox key at " +
37
+ "https://uphealth.us/signup and set UPHEALTH_API_KEY in your MCP client config.");
29
38
  }
30
- setFallbackContext({ bearer: apiKey });
39
+ setFallbackContext(ctx);
31
40
  const server = buildServer();
32
41
  const transport = new StdioServerTransport();
33
42
  await server.connect(transport);
package/dist/stdio.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":";AACA,uEAAuE;AACvE,4EAA4E;AAC5E,EAAE;AACF,gCAAgC;AAChC,EAAE;AACF,0CAA0C;AAC1C,EAAE;AACF,kCAAkC;AAClC,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,gEAAgE;AAChE,EAAE;AACF,+EAA+E;AAC/E,4EAA4E;AAC5E,kEAAkE;AAElE,OAAO,oBAAoB,CAAC,CAAC,6CAA6C;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,CAAC;IACpD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,iEAAiE;QACjE,OAAO,CAAC,KAAK,CACX,oEAAoE;YAClE,kFAAkF,CACrF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,kBAAkB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAEvC,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;IAC7B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,2EAA2E;IAC3E,kEAAkE;AACpE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":";AACA,uEAAuE;AACvE,4EAA4E;AAC5E,EAAE;AACF,gCAAgC;AAChC,EAAE;AACF,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,kFAAkF;AAClF,iEAAiE;AACjE,EAAE;AACF,kCAAkC;AAClC,EAAE;AACF,mFAAmF;AACnF,EAAE;AACF,kFAAkF;AAClF,+EAA+E;AAC/E,iFAAiF;AACjF,8EAA8E;AAC9E,kFAAkF;AAClF,iFAAiF;AACjF,kFAAkF;AAClF,kFAAkF;AAElF,OAAO,oBAAoB,CAAC,CAAC,6CAA6C;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,KAAK,UAAU,IAAI;IACjB,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC/D,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;QAChB,4EAA4E;QAC5E,2EAA2E;QAC3E,OAAO,CAAC,KAAK,CACX,wEAAwE;YACtE,0EAA0E;YAC1E,gFAAgF,CACnF,CAAC;IACJ,CAAC;IAED,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAExB,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;IAC7B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,2EAA2E;IAC3E,kEAAkE;AACpE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -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"}
@@ -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
- export function successResult(body) {
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(body, null, 2) }],
13
- structuredContent: body,
43
+ content: [{ type: "text", text: JSON.stringify(structured, null, 2) }],
44
+ structuredContent: structured,
14
45
  };
15
46
  }
16
47
  export function errorResult(message) {
@@ -1 +1 @@
1
- {"version":3,"file":"tool-result.js","sourceRoot":"","sources":["../src/tool-result.ts"],"names":[],"mappings":"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"}
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
- // v1 accepts only `general_wellness_daily` template + `display`
10
- // delivery_mode per the locked sandbox scope. Deliver-mode unlocks
11
- // at Per-Episode tier after BAA execution.
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 a patient. Returns stream_id + first cued message. Each subsequent cue MUST submit the prior message's feedback — the engine refuses to advance without it (the moat mechanic).",
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
- enum: ["general_wellness_daily"],
34
- description: "v1 sandbox supports one template; future templates land as v1+ ships.",
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: args,
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,gEAAgE;AAChE,mEAAmE;AACnE,2CAA2C;AAC3C,EAAE;AACF,iCAAiC;AACjC,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,+BAA+B,EAAE,MAAM,sBAAsB,CAAC;AAEvE,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EACT,0MAA0M;IAC5M,WAAW,EAAE;QACX,KAAK,EAAE,uBAAuB;QAC9B,YAAY,EAAE,KAAK;QACnB,eAAe,EAAE,KAAK;QACtB,aAAa,EAAE,IAAI;KACpB;IACD,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,WAAW,EAAE;gBACX,IAAI,EAAE,QAAiB;gBACvB,IAAI,EAAE,CAAC,wBAAwB,CAAC;gBAChC,WAAW,EAAE,uEAAuE;aACrF;YACD,UAAU,EAAE;gBACV,IAAI,EAAE,QAAiB;gBACvB,WAAW,EACT,mGAAmG;aACtG;YACD,aAAa,EAAE;gBACb,IAAI,EAAE,OAAgB;gBACtB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAiB,EAAE;gBAClC,WAAW,EAAE,qFAAqF;aACnG;YACD,aAAa,EAAE;gBACb,IAAI,EAAE,QAAiB;gBACvB,IAAI,EAAE,CAAC,SAAS,CAAC;gBACjB,OAAO,EAAE,SAAS;gBAClB,WAAW,EAAE,yEAAyE;aACvF;YACD,qBAAqB,EAAE;gBACrB,IAAI,EAAE,SAAkB;gBACxB,OAAO,EAAE,EAAE;aACZ;SACF;QACD,QAAQ,EAAE,CAAC,aAAa,CAAC;QACzB,oBAAoB,EAAE,IAAI;KAC3B;IACD,YAAY,EAAE,+BAA+B;IAC7C,KAAK,CAAC,OAAO,CAAC,IAA6B;QACzC,MAAM,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC;YAC/B,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,oBAAoB;YAC1B,MAAM;YACN,IAAI,EAAE,IAAI;SACX,CAAC,CAAC;QACH,IAAI,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACnC,OAAO,sBAAsB,CAC3B,QAAQ,CAAC,MAAM,EACf,QAAQ,CAAC,IAA6B,EACtC,uBAAuB,EACvB,QAAQ,CAAC,OAAO,CACjB,CAAC;QACJ,CAAC;QACD,OAAO,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;CACF,CAAC"}
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 prior message's feedback and receive the next cued message. THIS TOOL REFUSES WITHOUT PRIOR-MESSAGE FEEDBACK the moat. Same idempotency_key + same feedback returns the cached cue; conflicting feedback under the same key returns 409.",
19
+ description: "Submit the patient's response to the prior cue and get the next onefederally-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,uPAAuP;IACzP,WAAW,EAAE;QACX,KAAK,EAAE,wCAAwC;QAC/C,YAAY,EAAE,KAAK;QACnB,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;KACpB;IACD,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,SAAS,EAAE;gBACT,IAAI,EAAE,SAAkB;gBACxB,WAAW,EAAE,kDAAkD;aAChE;YACD,QAAQ,EAAE;gBACR,IAAI,EAAE,QAAiB;gBACvB,UAAU,EAAE;oBACV,eAAe,EAAE;wBACf,IAAI,EAAE,QAAiB;wBACvB,iEAAiE;wBACjE,2EAA2E;wBAC3E,4DAA4D;wBAC5D,IAAI,EAAE,CAAC,GAAG,oBAAoB,CAAC;qBAChC;oBACD,SAAS,EAAE,EAAE,IAAI,EAAE,QAAiB,EAAE;oBACtC,eAAe,EAAE;wBACf,IAAI,EAAE,QAAiB;wBACvB,MAAM,EAAE,MAAM;wBACd,WAAW,EACT,4HAA4H;qBAC/H;iBACF;gBACD,QAAQ,EAAE,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;aACjD;SACF;QACD,QAAQ,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC;QACnC,oBAAoB,EAAE,KAAK;KAC5B;IACD,YAAY,EAAE,8BAA8B;IAC5C,KAAK,CAAC,OAAO,CAAC,IAGb;QACC,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5E,OAAO,WAAW,CAChB,iEAAiE;gBAC/D,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CACjC,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACxD,OAAO,WAAW,CAChB,sHAAsH,CACvH,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC;YAC/B,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,sBAAsB,IAAI,CAAC,SAAS,MAAM;YAChD,MAAM;YACN,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE;SAClC,CAAC,CAAC;QACH,IAAI,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACnC,OAAO,sBAAsB,CAC3B,QAAQ,CAAC,MAAM,EACf,QAAQ,CAAC,IAA6B,EACtC,uBAAuB,EACvB,QAAQ,CAAC,OAAO,CACjB,CAAC;QACJ,CAAC;QACD,OAAO,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;CACF,CAAC"}
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,2FAA2F;QAC3F,4FAA4F;QAC5F,6FAA6F;QAC7F,sFAAsF;QACtF,sFAAsF;QACtF,0CAA0C;IAC5C,WAAW,EAAE;QACX,KAAK,EAAE,wCAAwC;QAC/C,YAAY,EAAE,KAAK;QACnB,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;KACpB;IACD,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,SAAS,EAAE;gBACT,IAAI,EAAE,SAAkB;gBACxB,WAAW,EAAE,kDAAkD;aAChE;YACD,QAAQ,EAAE;gBACR,IAAI,EAAE,QAAiB;gBACvB,UAAU,EAAE;oBACV,eAAe,EAAE;wBACf,IAAI,EAAE,QAAiB;wBACvB,iEAAiE;wBACjE,2EAA2E;wBAC3E,4DAA4D;wBAC5D,IAAI,EAAE,CAAC,GAAG,oBAAoB,CAAC;qBAChC;oBACD,SAAS,EAAE,EAAE,IAAI,EAAE,QAAiB,EAAE;oBACtC,eAAe,EAAE;wBACf,IAAI,EAAE,QAAiB;wBACvB,MAAM,EAAE,MAAM;wBACd,WAAW,EACT,4HAA4H;qBAC/H;iBACF;gBACD,QAAQ,EAAE,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;aACjD;SACF;QACD,QAAQ,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC;QACnC,oBAAoB,EAAE,KAAK;KAC5B;IACD,YAAY,EAAE,8BAA8B;IAC5C,KAAK,CAAC,OAAO,CAAC,IAGb;QACC,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5E,OAAO,WAAW,CAChB,iEAAiE;gBAC/D,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CACjC,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACxD,OAAO,WAAW,CAChB,sHAAsH,CACvH,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC;YAC/B,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,sBAAsB,IAAI,CAAC,SAAS,MAAM;YAChD,MAAM;YACN,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE;SAClC,CAAC,CAAC;QACH,IAAI,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACnC,OAAO,sBAAsB,CAC3B,QAAQ,CAAC,MAAM,EACf,QAAQ,CAAC,IAA6B,EACtC,uBAAuB,EACvB,QAAQ,CAAC,OAAO,CACjB,CAAC;QACJ,CAAC;QACD,OAAO,aAAa,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACxD,CAAC;CACF,CAAC"}
@@ -1,10 +1,14 @@
1
1
  // Track A A4 PR 2 — MCP tool registry.
2
- // All 4 v1 tools per docs/spec_track_a_a4_mcp_server.md §6.3.
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,