fix(revenuecat): 🐛 pick result frame from MCP SSE stream; cover platform sync/keys in scope-403

This commit is contained in:
amianthus
2026-06-04 16:48:27 +01:00
parent 36b1d7ce51
commit 0f21c1b01b
2 changed files with 67 additions and 9 deletions

View File

@@ -39,19 +39,33 @@ export const callRcMcpTool = async ({
throw new Error(`RevenueCat MCP error (${response.status})`);
}
// Streamable-HTTP MCP replies as SSE: pull the JSON out of the `data:` line.
// Streamable-HTTP MCP replies as SSE and may emit preamble frames (ping,
// progress) before the result — pick the frame that carries result/error.
const text = await response.text();
const dataLine = text
const candidates = text
.split("\n")
.map((line) => line.trim())
.find((line) => line.startsWith("data:") || line.startsWith("{"));
const payload = dataLine?.replace(/^data:\s*/, "") ?? text.trim();
.filter((line) => line.startsWith("data:") || line.startsWith("{"))
.map((line) => line.replace(/^data:\s*/, ""));
let parsed: JsonRpcResult;
try {
parsed = JSON.parse(payload) as JsonRpcResult;
} catch {
throw new Error(`RevenueCat MCP returned unparseable response: ${payload.slice(0, 200)}`);
let parsed: JsonRpcResult | undefined;
for (const candidate of candidates) {
let frame: JsonRpcResult;
try {
frame = JSON.parse(candidate) as JsonRpcResult;
} catch {
continue;
}
if (frame.result !== undefined || frame.error !== undefined) {
parsed = frame;
break;
}
}
if (!parsed) {
throw new Error(
`RevenueCat MCP returned no result frame: ${text.slice(0, 200)}`,
);
}
if (parsed.error) {

View File

@@ -1370,6 +1370,36 @@ const ROUTES = [
needsScopes: true,
isWebhookExempt: false,
},
{
handlerName: "handleSyncRevenueCat",
handlerFile:
"src/internal/platform/platformBeta/handlers/handleSyncRevenueCat.ts",
method: "POST",
path: "/v1/platform.sync_revenuecat",
style: "RPC",
group: "v1/platform",
mountChain: ["/v1", "", "", "/platform.sync_revenuecat"],
sourceRouterFile:
"src/internal/platform/platformBeta/platformRpcRouter.ts",
routeKind: "createRoute",
needsScopes: true,
isWebhookExempt: false,
},
{
handlerName: "handleGetRevenueCatKeys",
handlerFile:
"src/internal/platform/platformBeta/handlers/handleGetRevenueCatKeys.ts",
method: "POST",
path: "/v1/platform.get_revenuecat_keys",
style: "RPC",
group: "v1/platform",
mountChain: ["/v1", "", "", "/platform.get_revenuecat_keys"],
sourceRouterFile:
"src/internal/platform/platformBeta/platformRpcRouter.ts",
routeKind: "createRoute",
needsScopes: true,
isWebhookExempt: false,
},
{
handlerName: "handleCreateSchedule",
handlerFile: "src/internal/billing/v2/handlers/handleCreateSchedule.ts",
@@ -4269,6 +4299,20 @@ const SCOPE_DECISIONS: Record<
note: "platform RPC route — write",
decidedAt: "2026-06-01T00:00:00.000Z",
},
"POST|/v1/platform.sync_revenuecat|handleSyncRevenueCat": {
decision: "decided",
scopes: ["platform:write"],
shape: "array",
note: "platform RPC route — write",
decidedAt: "2026-06-01T00:00:00.000Z",
},
"POST|/v1/platform.get_revenuecat_keys|handleGetRevenueCatKeys": {
decision: "decided",
scopes: ["platform:write"],
shape: "array",
note: "platform RPC route — write",
decidedAt: "2026-06-01T00:00:00.000Z",
},
"POST|/v1/billing.create_schedule|handleCreateSchedule": {
decision: "decided",
scopes: ["billing:write"],