Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { initializeMcpServer } from "./mcp-handler.js";
import { warnOnLeakedKeys } from "./utils/key-leak-scanner.js";
import { installBlockrunMcpUserAgent } from "./utils/user-agent.js";
import { PROFILES } from "./profiles.js";

// Read version from package.json so it can never drift from the published version.
Expand Down Expand Up @@ -54,6 +55,7 @@ function handleCliMetadataFlags(argv: string[]): void {
}

handleCliMetadataFlags(process.argv);
installBlockrunMcpUserAgent(VERSION);

async function checkForUpdate() {
try {
Expand Down
41 changes: 41 additions & 0 deletions src/utils/user-agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const BLOCKRUN_HOSTS = new Set(["blockrun.ai", "www.blockrun.ai", "sol.blockrun.ai"]);
const INSTALL_MARKER = Symbol.for("blockrun.mcp.userAgentInstalled");

type MarkedGlobal = typeof globalThis & { [INSTALL_MARKER]?: boolean };

function requestUrl(input: RequestInfo | URL): URL | null {
try {
const raw = input instanceof Request ? input.url : input.toString();
return new URL(raw);
} catch {
return null;
}
}

/** Wrap fetch so every request to a BlockRun gateway is attributable to MCP. */
export function withBlockrunMcpUserAgent(
baseFetch: typeof fetch,
version: string,
): typeof fetch {
const userAgent = `blockrun-mcp/${version}`;
return (async (input: RequestInfo | URL, init?: RequestInit) => {
const url = requestUrl(input);
if (!url || !BLOCKRUN_HOSTS.has(url.hostname)) {
return baseFetch(input, init);
}

const headers = new Headers(input instanceof Request ? input.headers : undefined);
new Headers(init?.headers).forEach((value, key) => headers.set(key, value));
// Deliberately override the SDK's blockrun-ts/* identifier: this process is
// the MCP product boundary and should be counted as MCP in gateway logs.
headers.set("User-Agent", userAgent);
return baseFetch(input, { ...init, headers });
}) as typeof fetch;
}

export function installBlockrunMcpUserAgent(version: string): void {
const target = globalThis as MarkedGlobal;
if (target[INSTALL_MARKER]) return;
globalThis.fetch = withBlockrunMcpUserAgent(globalThis.fetch.bind(globalThis), version);
target[INSTALL_MARKER] = true;
}
46 changes: 46 additions & 0 deletions test/user-agent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import assert from "node:assert/strict";
import test from "node:test";
import { withBlockrunMcpUserAgent } from "../src/utils/user-agent.js";

function recorder() {
const calls: Array<{ input: RequestInfo | URL; init?: RequestInit }> = [];
const fetcher = (async (input: RequestInfo | URL, init?: RequestInit) => {
calls.push({ input, init });
return new Response("ok");
}) as typeof fetch;
return { calls, fetcher };
}

test("overrides SDK user-agent on BlockRun gateway requests", async () => {
const { calls, fetcher } = recorder();
const wrapped = withBlockrunMcpUserAgent(fetcher, "0.30.0");
await wrapped("https://blockrun.ai/api/v1/chat/completions", {
headers: { "User-Agent": "blockrun-ts/3.5.1", "X-Test": "kept" },
});

const headers = new Headers(calls[0].init?.headers);
assert.equal(headers.get("user-agent"), "blockrun-mcp/0.30.0");
assert.equal(headers.get("x-test"), "kept");
});

test("marks Solana gateway requests as MCP", async () => {
const { calls, fetcher } = recorder();
const wrapped = withBlockrunMcpUserAgent(fetcher, "0.30.0");
await wrapped("https://sol.blockrun.ai/api/v1/chat/completions");
assert.equal(
new Headers(calls[0].init?.headers).get("user-agent"),
"blockrun-mcp/0.30.0",
);
});

test("does not alter third-party requests", async () => {
const { calls, fetcher } = recorder();
const wrapped = withBlockrunMcpUserAgent(fetcher, "0.30.0");
await wrapped("https://registry.npmjs.org/@blockrun/mcp/latest", {
headers: { "User-Agent": "original" },
});
assert.equal(
new Headers(calls[0].init?.headers).get("user-agent"),
"original",
);
});