Merge branch 'feat/mcp-search-records' into release/v1.39.3

This commit is contained in:
Marc-André Bombeck committed 2026-09-26 17:00:45 +02:00
commit 22339aa38c
14 files changed
+3161 -141

No files matched your search

@@ -106,6 +106,16 @@ describe("GET /authorize — consent gate", () => {
expect(body).not.toMatch(/hlac_/);
});
it("names what the connection can read, document text included", async () => {
signedIn();
const body = await (await GET(getReq(authorizeUrl()) as never)).text();
// v1.39.3: search and fetch reach visits, conditions and the indexed
// text of stored documents, so the consent says so before Allow.
expect(body).toMatch(/visits/);
expect(body).toMatch(/text of your stored documents/);
expect(body).toMatch(/held back from AI reading/);
});
it("prompts sign-in (with a return link) when there is no session", async () => {
const res = await GET(getReq(authorizeUrl()) as never);
expect(res.status).toBe(200);
+5
View File
@@ -375,6 +375,10 @@ async function authorizeGet(request: NextRequest): Promise<Response> {
const accessSummary = writeGranted
? `<p><strong>This grants read AND write access:</strong> the application will be able to read your own health records <strong>and log new measurements and mood entries</strong> to your account on your behalf. It cannot delete or change existing entries, edit medications, or reach admin functions.</p>`
: `<p>Scope: <code>${htmlEscape(v.scope)}</code> — read-only access to your own health records.</p>`;
// What a read reaches, in plain words, before the person clicks Allow.
// Document text is named on its own because it is the widest thing a
// connection can read (v1.39.3: search and fetch reach it).
const readSummary = `<p>What it can read: your measurements, medications, lab values, visits and procedures, conditions and vaccinations, and your stored documents, including the text of your stored documents that HealthLog has indexed. It never receives the files themselves. A document you held back from AI reading is shared by its title and details only, without its text. Anything in a module you have switched off is not shared.</p>`;
// The one response whose form may lead off-origin: allow exactly the
// redirect URI `validate()` just matched against the resolved client.
return withAuthorizeCsp(
@@ -385,6 +389,7 @@ async function authorizeGet(request: NextRequest): Promise<Response> {
${provenance}
<p>Scope: <code>${htmlEscape(v.scope)}</code></p>
${accessSummary}
${readSummary}
<form method="POST" action="/api/mcp/oauth/authorize">
${hidden("response_type", "code")}
${hidden("client_id", v.clientId)}