diff --git a/netlify.toml b/netlify.toml index 8ac8f48..0d05dd0 100644 --- a/netlify.toml +++ b/netlify.toml @@ -53,6 +53,11 @@ to = "/.netlify/functions/snapshots?key=:key" status = 200 +[[redirects]] + from = "/api/mcp-context" + to = "/.netlify/functions/mcp-context" + status = 200 + # Swagger UI [[redirects]] from = "/docs" diff --git a/netlify/functions/helpers.js b/netlify/functions/helpers.js index c5d50a1..641922a 100644 --- a/netlify/functions/helpers.js +++ b/netlify/functions/helpers.js @@ -58,4 +58,14 @@ async function getLatestSnapshot() { return { key: latestKey, data }; } -module.exports = { STORE_NAME, json, error, options, getLatestSnapshot, listBlobs, getBlob }; +async function putBlob(store, key, value, contentType = "application/json") { + const body = contentType === "application/json" ? JSON.stringify(value) : value; + const resp = await fetch(blobUrl(store, key), { + method: "PUT", + headers: { ...blobHeaders(), "Content-Type": contentType }, + body, + }); + if (!resp.ok) throw new Error(`Put blob failed: ${resp.status}`); +} + +module.exports = { STORE_NAME, json, error, options, getLatestSnapshot, listBlobs, getBlob, putBlob, blobUrl, blobHeaders, BLOB_SITE_ID }; diff --git a/netlify/functions/mcp-context.js b/netlify/functions/mcp-context.js new file mode 100644 index 0000000..037f8f7 --- /dev/null +++ b/netlify/functions/mcp-context.js @@ -0,0 +1,49 @@ +const { json, error, options, listBlobs, blobUrl, blobHeaders } = require("./helpers"); + +const ONCALL_STORE = "oncall"; + +exports.handler = async (event) => { + if (event.httpMethod === "OPTIONS") return options(); + + try { + const service = event.queryStringParameters?.service; + + if (!service) { + const blobs = await listBlobs(ONCALL_STORE); + const entries = blobs.map((b) => { + const parts = b.key.split("/"); + return { key: b.key, service: parts[0], timestamp: parts.slice(1).join("/") }; + }); + return json({ count: entries.length, entries }); + } + + const blobs = await listBlobs(ONCALL_STORE); + const serviceBlobs = blobs.filter((b) => b.key.startsWith(`${service}/`)); + + if (!serviceBlobs.length) { + return error(`No oncall logs found for service: ${service}`, 404); + } + + const keys = serviceBlobs.map((b) => b.key).sort(); + const latestKey = keys[keys.length - 1]; + + const resp = await fetch(blobUrl(ONCALL_STORE, latestKey), { + headers: blobHeaders(), + }); + if (!resp.ok) return error(`Failed to fetch oncall log: ${resp.status}`, 500); + + const ct = resp.headers.get("content-type") || ""; + const content = ct.includes("application/json") + ? await resp.json() + : await resp.text(); + + return json({ + service, + key: latestKey, + available_logs: keys, + latest_log: content, + }); + } catch (e) { + return error(`Failed to fetch oncall context: ${e.message}`); + } +}; diff --git a/public/swagger/openapi.yaml b/public/swagger/openapi.yaml index b803bd1..028761e 100644 --- a/public/swagger/openapi.yaml +++ b/public/swagger/openapi.yaml @@ -147,6 +147,36 @@ paths: schema: $ref: "#/components/schemas/Error" + /mcp-context: + get: + summary: Oncall service context + description: | + Returns oncall chat logs for a given service. Without a service parameter, + lists all available oncall entries. With a service name, returns the latest + log and all available timestamps. + operationId: getMcpContext + tags: [Oncall] + parameters: + - name: service + in: query + required: false + description: "Service name (e.g. allocation-engine-2.0)" + schema: + type: string + responses: + "200": + description: Oncall context logs + content: + application/json: + schema: + $ref: "#/components/schemas/McpContextResponse" + "404": + description: No oncall logs found for service + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + components: schemas: Error: @@ -433,6 +463,38 @@ components: type: string description: Snapshot keys (most recent 50), newest first + McpContextResponse: + type: object + properties: + service: + type: string + example: allocation-engine-2.0 + key: + type: string + description: Blob key for the latest oncall log + available_logs: + type: array + items: + type: string + description: All available log keys for this service + latest_log: + description: Content of the latest oncall chat log (text or JSON) + count: + type: integer + description: Total number of oncall entries (when listing all) + entries: + type: array + items: + type: object + properties: + key: + type: string + service: + type: string + timestamp: + type: string + description: All oncall entries across services (when listing all) + SnapshotResponse: type: object properties: