From 1acc362075e7b5fc5c19bb51380074cf260ac743 Mon Sep 17 00:00:00 2001 From: Rares Astilean Date: Sat, 29 Aug 2026 19:05:15 +0300 Subject: [PATCH 1/2] PRO-1042: identify platform MCP API calls --- src/api-client.mjs | 1 + tests/api-client.test.mjs | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/api-client.mjs b/src/api-client.mjs index 930b817..017e168 100644 --- a/src/api-client.mjs +++ b/src/api-client.mjs @@ -35,6 +35,7 @@ export async function apiRequest(path, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', + 'X-Lua-Client': 'platform-mcp/1.0.0', }, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, diff --git a/tests/api-client.test.mjs b/tests/api-client.test.mjs index 3194163..ebc7a68 100644 --- a/tests/api-client.test.mjs +++ b/tests/api-client.test.mjs @@ -4,8 +4,21 @@ // 401 / 403 / generic-error paths, and query-string handling. import { describe, test, expect, beforeEach, afterEach } from '@jest/globals'; +import { readFileSync, readdirSync } from 'node:fs'; +import { join, relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; import { apiRequest } from '../src/api-client.mjs'; +const SOURCE_DIRECTORY = fileURLToPath(new URL('../src/', import.meta.url)); +const MCP_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')); + +function sourceFiles(directory) { + return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { + const path = join(directory, entry.name); + return entry.isDirectory() ? sourceFiles(path) : [path]; + }); +} + function mockFetch(scripted) { const calls = []; const fn = async (url, init) => { @@ -61,6 +74,22 @@ describe('apiRequest', () => { expect(fetchFn.calls[0].init.headers['Content-Type']).toBe('application/json'); }); + test('identifies direct requests as the versioned platform MCP client', async () => { + const fetchFn = mockFetch(jsonResponse({ ok: true })); + await apiRequest('/agents', { fetchFn }); + expect(fetchFn.calls[0].init.headers['X-Lua-Client']).toBe(`platform-mcp/${MCP_PACKAGE.version}`); + }); + + test('keeps every direct Lua API call behind the identified wrapper', () => { + const directCallers = sourceFiles(SOURCE_DIRECTORY) + .filter((path) => path.endsWith('.mjs')) + .filter((path) => /\b(?:fetch|fetchFn)\s*\(/.test(readFileSync(path, 'utf8'))) + .map((path) => relative(SOURCE_DIRECTORY, path)) + .sort(); + + expect(directCallers).toEqual(['api-client.mjs']); + }); + test('uses LUA_API_URL env override when set', async () => { process.env.LUA_API_URL = 'https://api-staging.heylua.ai'; const fetchFn = mockFetch(jsonResponse({ ok: true })); From 64c65a4d4fdd601ab96e5c497f1ca194d749162a Mon Sep 17 00:00:00 2001 From: Rares Astilean Date: Sun, 30 Aug 2026 09:26:13 +0300 Subject: [PATCH 2/2] fix(client): derive platform MCP release identity --- src/api-client.mjs | 7 +++++- tests/api-client.test.mjs | 45 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/api-client.mjs b/src/api-client.mjs index 017e168..116ca02 100644 --- a/src/api-client.mjs +++ b/src/api-client.mjs @@ -6,9 +6,14 @@ // against packages/lua-api source by the M4 contract tests. import { resolveApiKey } from './auth.mjs'; +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); +const { version: PACKAGE_VERSION } = require('../package.json'); const DEFAULT_BASE_URL = 'https://api.heylua.ai'; const DEFAULT_TIMEOUT_MS = 10_000; +const CLIENT_IDENTITY = `platform-mcp/${PACKAGE_VERSION}`; /** * @param {string} path - API path (with leading slash) @@ -35,7 +40,7 @@ export async function apiRequest(path, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', - 'X-Lua-Client': 'platform-mcp/1.0.0', + 'X-Lua-Client': CLIENT_IDENTITY, }, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, diff --git a/tests/api-client.test.mjs b/tests/api-client.test.mjs index ebc7a68..fa7e60e 100644 --- a/tests/api-client.test.mjs +++ b/tests/api-client.test.mjs @@ -7,6 +7,7 @@ import { describe, test, expect, beforeEach, afterEach } from '@jest/globals'; import { readFileSync, readdirSync } from 'node:fs'; import { join, relative } from 'node:path'; import { fileURLToPath } from 'node:url'; +import ts from 'typescript'; import { apiRequest } from '../src/api-client.mjs'; const SOURCE_DIRECTORY = fileURLToPath(new URL('../src/', import.meta.url)); @@ -19,6 +20,42 @@ function sourceFiles(directory) { }); } +const NETWORK_MODULES = new Set([ + 'axios', + 'got', + 'ky', + 'node-fetch', + 'node:http', + 'node:https', + 'undici', +]); + +function directTransportEvidence(path) { + const source = readFileSync(path, 'utf8'); + const file = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); + const evidence = []; + + function visit(node) { + if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) { + if (NETWORK_MODULES.has(node.moduleSpecifier.text)) { + evidence.push(`import:${node.moduleSpecifier.text}`); + } + } + if (ts.isCallExpression(node)) { + if (ts.isIdentifier(node.expression) && ['fetch', 'fetchFn'].includes(node.expression.text)) { + evidence.push(`call:${node.expression.text}`); + } + if (ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'fetch') { + evidence.push(`call:${node.expression.getText(file)}`); + } + } + ts.forEachChild(node, visit); + } + + visit(file); + return evidence; +} + function mockFetch(scripted) { const calls = []; const fn = async (url, init) => { @@ -77,13 +114,17 @@ describe('apiRequest', () => { test('identifies direct requests as the versioned platform MCP client', async () => { const fetchFn = mockFetch(jsonResponse({ ok: true })); await apiRequest('/agents', { fetchFn }); - expect(fetchFn.calls[0].init.headers['X-Lua-Client']).toBe(`platform-mcp/${MCP_PACKAGE.version}`); + expect(fetchFn.calls[0].init.headers).toEqual({ + 'Authorization': 'Bearer lk_test_key', + 'Content-Type': 'application/json', + 'X-Lua-Client': `platform-mcp/${MCP_PACKAGE.version}`, + }); }); test('keeps every direct Lua API call behind the identified wrapper', () => { const directCallers = sourceFiles(SOURCE_DIRECTORY) .filter((path) => path.endsWith('.mjs')) - .filter((path) => /\b(?:fetch|fetchFn)\s*\(/.test(readFileSync(path, 'utf8'))) + .filter((path) => directTransportEvidence(path).length > 0) .map((path) => relative(SOURCE_DIRECTORY, path)) .sort();