- Adicionado estrutura completa do projeto - Configurado MCP server para Premiere Pro - Adicionado documentação e skills - Configurado Gitignore para o projeto
223 lines
10 KiB
TypeScript
Executable File
223 lines
10 KiB
TypeScript
Executable File
import { EventEmitter } from "node:events";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
HttpAdmissionController,
|
|
RequestBodyTooLargeError,
|
|
exceedsRequestBodyLimit,
|
|
getRequestPathname,
|
|
isAuthorizedBearer,
|
|
isSupportedMcpMethod,
|
|
rateLimitIdentity,
|
|
readBoundedRequestBody,
|
|
readHttpAdmissionSettings,
|
|
readHttpAuthConfiguration,
|
|
requestContentLength,
|
|
} from "../src/http-admission.js";
|
|
|
|
describe("HTTP admission settings", () => {
|
|
it("uses bounded secure defaults and rejects invalid limits", () => {
|
|
expect(readHttpAdmissionSettings({})).toMatchObject({
|
|
maxRequestBytes: 1_048_576,
|
|
maxConcurrentRequests: 8,
|
|
maxConcurrentStreams: 32,
|
|
rateLimitPerMinute: 120,
|
|
rateLimitBurst: 30,
|
|
});
|
|
expect(() => readHttpAdmissionSettings({ MCP_MAX_REQUEST_BYTES: "unlimited" })).toThrow("MCP_MAX_REQUEST_BYTES");
|
|
expect(() => readHttpAdmissionSettings({ MCP_RATE_LIMIT_PER_MINUTE: "10", MCP_RATE_LIMIT_BURST: "11" })).toThrow("MCP_RATE_LIMIT_BURST");
|
|
});
|
|
|
|
it("never permits unauthenticated HTTP in production", () => {
|
|
expect(readHttpAuthConfiguration({ MCP_AUTH_TOKEN: "secret", NODE_ENV: "production" })).toEqual({
|
|
mode: "shared-token", authToken: "secret", allowUnauthenticated: false,
|
|
});
|
|
expect(readHttpAuthConfiguration({ ALLOW_UNAUTHENTICATED: "1", NODE_ENV: "test" })).toEqual({
|
|
mode: "unauthenticated", allowUnauthenticated: true,
|
|
});
|
|
expect(() => readHttpAuthConfiguration({ ALLOW_UNAUTHENTICATED: "1", NODE_ENV: "production" })).toThrow("MCP_AUTH_TOKEN");
|
|
});
|
|
|
|
it("requires complete HTTPS OAuth configuration and rejects ambiguous auth modes", () => {
|
|
const oauth = readHttpAuthConfiguration({
|
|
NODE_ENV: "production",
|
|
MCP_OAUTH_ISSUER: "https://identity.example.com",
|
|
MCP_OAUTH_AUDIENCE: "https://premiere.example.com/mcp",
|
|
MCP_OAUTH_JWKS_URI: "https://identity.example.com/.well-known/jwks.json",
|
|
MCP_PUBLIC_URL: "https://premiere.example.com",
|
|
MCP_OAUTH_REQUIRED_SCOPES: "premiere:mcp premiere:read",
|
|
MCP_OAUTH_ALLOWED_SUBJECTS: "user-1,user-2",
|
|
});
|
|
expect(oauth).toEqual({
|
|
mode: "oauth",
|
|
allowUnauthenticated: false,
|
|
oauth: {
|
|
issuer: "https://identity.example.com",
|
|
audience: "https://premiere.example.com/mcp",
|
|
jwksUri: "https://identity.example.com/.well-known/jwks.json",
|
|
publicUrl: "https://premiere.example.com",
|
|
requiredScopes: ["premiere:mcp", "premiere:read"],
|
|
allowedSubjects: ["user-1", "user-2"],
|
|
},
|
|
});
|
|
expect(() => readHttpAuthConfiguration({
|
|
NODE_ENV: "production", MCP_OAUTH_ISSUER: "https://identity.example.com",
|
|
})).toThrow("all required");
|
|
expect(() => readHttpAuthConfiguration({
|
|
NODE_ENV: "production",
|
|
MCP_OAUTH_ISSUER: "http://identity.example.com",
|
|
MCP_OAUTH_AUDIENCE: "https://premiere.example.com/mcp",
|
|
MCP_OAUTH_JWKS_URI: "https://identity.example.com/jwks.json",
|
|
MCP_PUBLIC_URL: "https://premiere.example.com",
|
|
MCP_OAUTH_ALLOWED_SUBJECTS: "user-1",
|
|
})).toThrow("HTTPS");
|
|
expect(() => readHttpAuthConfiguration({
|
|
NODE_ENV: "production",
|
|
MCP_AUTH_TOKEN: "legacy",
|
|
MCP_OAUTH_ISSUER: "https://identity.example.com",
|
|
})).toThrow("either MCP_AUTH_TOKEN or MCP_OAUTH_ISSUER");
|
|
expect(() => readHttpAuthConfiguration({
|
|
NODE_ENV: "production",
|
|
MCP_AUTH_TOKEN: "legacy",
|
|
MCP_OAUTH_REQUIRED_SCOPES: "premiere:mcp",
|
|
})).toThrow("either MCP_AUTH_TOKEN or MCP_OAUTH_ISSUER");
|
|
expect(() => readHttpAuthConfiguration({
|
|
NODE_ENV: "production",
|
|
MCP_OAUTH_ISSUER: "https://identity.example.com",
|
|
MCP_OAUTH_AUDIENCE: "https://other.example.com/mcp",
|
|
MCP_OAUTH_JWKS_URI: "https://identity.example.com/jwks.json",
|
|
MCP_PUBLIC_URL: "https://premiere.example.com",
|
|
MCP_OAUTH_ALLOWED_SUBJECTS: "user-1",
|
|
})).toThrow("exactly equal");
|
|
});
|
|
});
|
|
|
|
describe("HTTP request classification", () => {
|
|
it("accepts only the exact MCP pathname and supported methods", () => {
|
|
expect(getRequestPathname("/mcp?client=desktop")).toBe("/mcp");
|
|
expect(getRequestPathname("/mcp-typo")).not.toBe("/mcp");
|
|
expect(getRequestPathname("/mcp%2Fprivate")).not.toBe("/mcp");
|
|
expect(getRequestPathname(undefined)).toBeUndefined();
|
|
expect(getRequestPathname("http://[")).toBeUndefined();
|
|
expect(isSupportedMcpMethod("POST")).toBe(true);
|
|
expect(isSupportedMcpMethod("PUT")).toBe(false);
|
|
expect(isSupportedMcpMethod(undefined)).toBe(false);
|
|
});
|
|
|
|
it("rejects invalid or over-limit declared body sizes before parsing", () => {
|
|
expect(exceedsRequestBodyLimit({ headers: { "content-length": "1024" } } as never, 1024)).toBe(false);
|
|
expect(exceedsRequestBodyLimit({ headers: { "content-length": "1025" } } as never, 1024)).toBe(true);
|
|
expect(exceedsRequestBodyLimit({ headers: { "content-length": "unknown" } } as never, 1024)).toBe(true);
|
|
expect(requestContentLength({ headers: {} } as never)).toBeUndefined();
|
|
expect(requestContentLength({ headers: { "content-length": ["1024", "2048"] } } as never)).toBe(1024);
|
|
expect(Number.isNaN(requestContentLength({ headers: { "content-length": "9007199254740992" } } as never))).toBe(true);
|
|
});
|
|
|
|
it("bounds chunked bodies before transport parsing", async () => {
|
|
const request = Object.assign(new EventEmitter(), {
|
|
resume: vi.fn(),
|
|
});
|
|
const body = readBoundedRequestBody(request as never, 4);
|
|
request.emit("data", Buffer.from("four"));
|
|
request.emit("data", Buffer.from("!"));
|
|
await expect(body).rejects.toBeInstanceOf(RequestBodyTooLargeError);
|
|
expect(request.resume).toHaveBeenCalledOnce();
|
|
|
|
const complete = Object.assign(new EventEmitter(), { resume: vi.fn() });
|
|
const completeBody = readBoundedRequestBody(complete as never, 4);
|
|
complete.emit("data", Buffer.from("ok"));
|
|
complete.emit("data", "!");
|
|
complete.emit("end");
|
|
await expect(completeBody).resolves.toEqual(Buffer.from("ok!"));
|
|
});
|
|
|
|
it("compares bearer credentials without treating malformed headers as authorized", () => {
|
|
const request = (authorization?: string) => ({ headers: { authorization } } as never);
|
|
expect(isAuthorizedBearer(request("Bearer a-strong-token"), "a-strong-token")).toBe(true);
|
|
expect(isAuthorizedBearer(request("Bearer wrong"), "a-strong-token")).toBe(false);
|
|
expect(isAuthorizedBearer(request("Basic a-strong-token"), "a-strong-token")).toBe(false);
|
|
expect(isAuthorizedBearer(request(), "a-strong-token")).toBe(false);
|
|
expect(isAuthorizedBearer({ headers: { authorization: ["Bearer a-strong-token"] } } as never, "a-strong-token")).toBe(true);
|
|
expect(isAuthorizedBearer(request(), undefined)).toBe(true);
|
|
});
|
|
|
|
it("does not trust forwarded addresses unless configured", () => {
|
|
const request = {
|
|
headers: { "x-forwarded-for": "198.51.100.4, 10.0.0.1" },
|
|
socket: { remoteAddress: "10.0.0.1" },
|
|
} as never;
|
|
expect(rateLimitIdentity(request, false)).not.toBe(rateLimitIdentity(request, true));
|
|
expect(rateLimitIdentity({ headers: { "x-forwarded-for": ["203.0.113.8"] }, socket: { remoteAddress: "10.0.0.1" } } as never, true)).toMatch(/^ip:/);
|
|
expect(rateLimitIdentity({ headers: {}, socket: { remoteAddress: "" } } as never, false)).toMatch(/^ip:/);
|
|
expect(rateLimitIdentity({ headers: {}, socket: {} } as never, true)).toMatch(/^ip:/);
|
|
});
|
|
});
|
|
|
|
describe("HTTP admission controller", () => {
|
|
it("bounds concurrent requests and makes release idempotent", () => {
|
|
const controller = new HttpAdmissionController({
|
|
maxConcurrentRequests: 1, maxConcurrentStreams: 2, rateLimitPerMinute: 100, rateLimitBurst: 10, maxRateLimitKeys: 16,
|
|
});
|
|
const first = controller.acquire("credential:a");
|
|
expect(first.accepted).toBe(true);
|
|
const second = controller.acquire("credential:b");
|
|
expect(second).toMatchObject({ accepted: false, reason: "at_capacity", statusCode: 503 });
|
|
if (first.accepted) {
|
|
first.release();
|
|
first.release();
|
|
}
|
|
expect(controller.metrics().activeRequests).toBe(0);
|
|
expect(controller.acquire("credential:b").accepted).toBe(true);
|
|
});
|
|
|
|
it("enforces a bounded token bucket with Retry-After", () => {
|
|
let now = 0;
|
|
const controller = new HttpAdmissionController({
|
|
maxConcurrentRequests: 5, maxConcurrentStreams: 5, rateLimitPerMinute: 60, rateLimitBurst: 2, maxRateLimitKeys: 16,
|
|
}, () => now);
|
|
const first = controller.acquire("credential:a");
|
|
const second = controller.acquire("credential:a");
|
|
if (first.accepted) first.release();
|
|
if (second.accepted) second.release();
|
|
expect(controller.acquire("credential:a")).toMatchObject({ accepted: false, statusCode: 429, retryAfterSeconds: 1 });
|
|
now = 1_000;
|
|
expect(controller.acquire("credential:a").accepted).toBe(true);
|
|
});
|
|
|
|
it("bounds tracked identities and prunes stale buckets", () => {
|
|
let now = 0;
|
|
const controller = new HttpAdmissionController({
|
|
maxConcurrentRequests: 2, maxConcurrentStreams: 2, rateLimitPerMinute: 60, rateLimitBurst: 1, maxRateLimitKeys: 1,
|
|
}, () => now);
|
|
const first = controller.acquire("credential:a");
|
|
if (first.accepted) first.release();
|
|
expect(controller.acquire("credential:b")).toMatchObject({ accepted: false, reason: "rate_limited" });
|
|
now = 120_001;
|
|
const second = controller.acquire("credential:b");
|
|
expect(second.accepted).toBe(true);
|
|
expect(controller.metrics().trackedRateLimitKeys).toBe(1);
|
|
if (second.accepted) second.release();
|
|
});
|
|
|
|
it("reserves SSE capacity without consuming operation capacity", () => {
|
|
const controller = new HttpAdmissionController({
|
|
maxConcurrentRequests: 1, maxConcurrentStreams: 2, rateLimitPerMinute: 100, rateLimitBurst: 10, maxRateLimitKeys: 16,
|
|
});
|
|
const operation = controller.acquire("credential:a", "operation");
|
|
const firstStream = controller.acquire("credential:a", "stream");
|
|
const secondStream = controller.acquire("credential:b", "stream");
|
|
expect(operation.accepted).toBe(true);
|
|
expect(firstStream.accepted).toBe(true);
|
|
expect(secondStream.accepted).toBe(true);
|
|
expect(controller.acquire("credential:b", "operation")).toMatchObject({ accepted: false, reason: "at_capacity" });
|
|
expect(controller.acquire("credential:c", "stream")).toMatchObject({ accepted: false, reason: "at_capacity" });
|
|
expect(controller.metrics()).toMatchObject({
|
|
activeRequests: 3,
|
|
activeOperationRequests: 1,
|
|
activeStreamRequests: 2,
|
|
});
|
|
if (operation.accepted) operation.release();
|
|
if (firstStream.accepted) firstStream.release();
|
|
if (secondStream.accepted) secondStream.release();
|
|
});
|
|
});
|