Skip to content

Commit aaf0f36

Browse files
authored
1.0.5
- Added X-Lara-Client and X-Lara-Client-Version headers to Lara API calls
1 parent 86f5173 commit aaf0f36

7 files changed

Lines changed: 105 additions & 19 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@translated/lara-mcp",
3-
"version": "1.0.4",
3+
"version": "1.0.5",
44
"description": "Lara API official MCP server",
55
"author": {
66
"name": "Translated",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, it, expect, beforeEach, vi } from "vitest";
2+
import { PACKAGE_VERSION } from "../../version.js";
3+
4+
// Hoisted so it exists before the mock factory runs (the factory fires as soon
5+
// as anything imports "@translated/lara", which happens transitively before
6+
// module-scope consts would be initialized).
7+
const { instances } = vi.hoisted(() => ({ instances: [] as any[] }));
8+
9+
vi.mock("@translated/lara", async (importOriginal) => {
10+
const actual = await importOriginal<typeof import("@translated/lara")>();
11+
return {
12+
...actual,
13+
Translator: vi.fn(() => {
14+
const instance = { client: { setExtraHeader: vi.fn() } };
15+
instances.push(instance);
16+
return instance;
17+
}),
18+
};
19+
});
20+
21+
const { default: getMcpServer } = await import("../../mcp/server.js");
22+
23+
describe("getMcpServer Lara client headers", () => {
24+
beforeEach(() => {
25+
instances.length = 0;
26+
});
27+
28+
it("sets X-Lara-Client and X-Lara-Client-Version on every SDK call", () => {
29+
getMcpServer("test-id", "test-secret");
30+
31+
expect(instances).toHaveLength(1);
32+
const setExtraHeader = instances[0].client.setExtraHeader;
33+
34+
expect(setExtraHeader).toHaveBeenCalledWith("X-Lara-Client", "MCP");
35+
expect(setExtraHeader).toHaveBeenCalledWith(
36+
"X-Lara-Client-Version",
37+
PACKAGE_VERSION
38+
);
39+
expect(setExtraHeader).toHaveBeenCalledTimes(2);
40+
});
41+
});

src/__tests__/utils/mocks.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ export function createMockTranslator() {
4646
addOrReplaceEntry: vi.fn(),
4747
deleteEntry: vi.fn(),
4848
},
49-
client: {}
49+
client: {
50+
setExtraHeader: vi.fn(),
51+
}
5052
};
5153
}
5254

src/__tests__/version.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { describe, it, expect } from "vitest";
2+
import { readFileSync } from "node:fs";
3+
import { fileURLToPath } from "node:url";
4+
import { dirname, join } from "node:path";
5+
import { PACKAGE_VERSION } from "../version.js";
6+
7+
describe("PACKAGE_VERSION", () => {
8+
it("matches the version declared in package.json", () => {
9+
const here = dirname(fileURLToPath(import.meta.url));
10+
const pkg = JSON.parse(
11+
readFileSync(join(here, "..", "..", "package.json"), "utf8")
12+
) as { version: string };
13+
14+
expect(PACKAGE_VERSION).toBe(pkg.version);
15+
expect(PACKAGE_VERSION).not.toBe("unknown");
16+
});
17+
});

src/mcp/server.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { CallTool, ListTools } from "./tools.js";
1111
import { ListResources, ListResourceTemplates, ReadResource } from "./resources.js";
1212
import { logger } from "#logger";
13+
import { PACKAGE_VERSION } from "../version.js";
1314

1415
export default function getMcpServer(
1516
accessKeyId: string,
@@ -20,10 +21,26 @@ export default function getMcpServer(
2021
const credentials = new Credentials(accessKeyId, accessKeySecret);
2122
const lara = new Translator(credentials);
2223

24+
// Identify MCP-originated traffic to Lara on every SDK request. extraHeaders
25+
// are spread into all requests by the SDK transport, so setting them once
26+
// here covers translate, glossaries, memories, languages, imports, etc. The
27+
// client is protected/internal in the SDK types, hence the narrow cast.
28+
const laraClient = (lara as unknown as {
29+
client?: { setExtraHeader?: (name: string, value: string) => void };
30+
}).client;
31+
if (typeof laraClient?.setExtraHeader === "function") {
32+
laraClient.setExtraHeader("X-Lara-Client", "MCP");
33+
laraClient.setExtraHeader("X-Lara-Client-Version", PACKAGE_VERSION);
34+
} else {
35+
logger.warn(
36+
"Lara SDK client does not expose setExtraHeader; X-Lara-Client headers not set"
37+
);
38+
}
39+
2340
const server = new Server(
2441
{
2542
name: "Lara Translate",
26-
version: "0.0.15",
43+
version: PACKAGE_VERSION,
2744
},
2845
{
2946
capabilities: {

src/rest/routes/server-info.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,12 @@
1-
import fs from "node:fs";
2-
import path from "node:path";
3-
41
import express from "express";
52
import { RestServer } from "#rest/server";
6-
7-
const buildInfoJson: string = fs.readFileSync(
8-
path.join(import.meta.dirname, "..", "..", "..", "package.json"),
9-
"utf8"
10-
);
11-
12-
type BuildInfo = {
13-
name: string;
14-
version: string;
15-
};
16-
17-
export const BuildInfo: BuildInfo = JSON.parse(buildInfoJson);
3+
import { PACKAGE_VERSION } from "../../version.js";
184

195
export default function serverInfoRouter(rest: RestServer): express.Router {
206
const router = express.Router();
217

228
router.all("/", (_req, res) => {
23-
rest.send(res, { version: BuildInfo.version });
9+
rest.send(res, { version: PACKAGE_VERSION });
2410
});
2511

2612
return router;

src/version.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { readFileSync } from "node:fs";
2+
import { join } from "node:path";
3+
4+
// Read this package's version once at module load so it can be sent as the
5+
// X-Lara-Client-Version header on every Lara SDK call and reported by the
6+
// server-info route. package.json sits next to the source dir in dev
7+
// (src/version.ts) and next to dist/ in the published package (dist/version.js),
8+
// so "../package.json" resolves in both layouts. Fall back to "unknown" so a
9+
// missing/unreadable file degrades gracefully instead of crashing on import.
10+
function loadPackageVersion(): string {
11+
try {
12+
const pkg = JSON.parse(
13+
readFileSync(join(import.meta.dirname, "..", "package.json"), "utf8")
14+
) as { version?: unknown };
15+
return typeof pkg.version === "string" && pkg.version.length > 0
16+
? pkg.version
17+
: "unknown";
18+
} catch {
19+
return "unknown";
20+
}
21+
}
22+
23+
export const PACKAGE_VERSION = loadPackageVersion();

0 commit comments

Comments
 (0)