import * as NodeServices from "@effect/platform-node/NodeServices";import { expect, it } from "@effect/vitest";import { EnvironmentId, ProviderInstanceId, ThreadId } from "@t3tools/contracts";import * as Effect from "effect/Effect";import { HttpServer } from "effect/unstable/http"; import * as ServerEnvironment from "../environment/ServerEnvironment.ts";import * as McpSessionRegistry from "./McpSessionRegistry.ts"; const environmentId = EnvironmentId.make("environment-1");const makeFakeHttpServer = (hostname: string, port = 43123) => HttpServer.HttpServer.of({ address: { _tag: "TcpAddress", hostname, port }, serve: (() => Effect.void) as HttpServer.HttpServer["Service"]["serve"], });const fakeHttpServer = makeFakeHttpServer("127.0.0.1");const fakeEnvironment = ServerEnvironment.ServerEnvironment.of({ getEnvironmentId: Effect.succeed(environmentId), getDescriptor: Effect.die("unused"),}); const makeRegistry = (now: () => number, httpServer = fakeHttpServer) => McpSessionRegistry.__testing .make({ now, livenessWindowMs: 100, }) .pipe( Effect.provideService(HttpServer.HttpServer, httpServer), Effect.provideService(ServerEnvironment.ServerEnvironment, fakeEnvironment), Effect.provide(NodeServices.layer), ); it.effect("stores only a token hash, resolves the bearer token, and revokes by thread", () => Effect.gen(function* () { let timestamp = 1_000; const registry = yield* makeRegistry(() => timestamp); const threadId = ThreadId.make("thread-1"); const issued = yield* registry.issue({ threadId, providerInstanceId: ProviderInstanceId.make("codex"), }); expect(issued.config.endpoint).toBe("http://127.0.0.1:43123/mcp"); const token = issued.config.authorizationHeader.replace(/^Bearer\s+/, ""); expect(token.length).toBeGreaterThan(20); const resolved = yield* registry.resolve(token); expect(resolved?.threadId).toBe(threadId); yield* registry.revokeThread(threadId); expect(yield* registry.resolve(token)).toBeUndefined(); timestamp += 2_000; }),); it.effect("builds MCP endpoints from the bound server host", () => Effect.gen(function* () { const cases = [ ["100.64.0.40", "http://100.64.0.40:43123/mcp"], ["0.0.0.0", "http://127.0.0.1:43123/mcp"], ["localhost", "http://localhost:43123/mcp"], ["127.0.0.1", "http://127.0.0.1:43123/mcp"], ] as const; for (const [hostname, expectedEndpoint] of cases) { const registry = yield* makeRegistry(() => 1_000, makeFakeHttpServer(hostname)); const issued = yield* registry.issue({ threadId: ThreadId.make(`thread-${hostname}`), providerInstanceId: ProviderInstanceId.make("codex"), }); expect(issued.config.endpoint).toBe(expectedEndpoint); } }),); it.effect("expires credentials once their session stops showing signs of life", () => Effect.gen(function* () { let timestamp = 1_000; const registry = yield* makeRegistry(() => timestamp); const issued = yield* registry.issue({ threadId: ThreadId.make("thread-2"), providerInstanceId: ProviderInstanceId.make("claude"), }); const token = issued.config.authorizationHeader.replace(/^Bearer\s+/, ""); timestamp += 101; expect(yield* registry.resolve(token)).toBeUndefined(); }),); it.effect("keeps a credential alive across turns that never touch an MCP tool", () => Effect.gen(function* () { let timestamp = 1_000; const registry = yield* makeRegistry(() => timestamp); const threadId = ThreadId.make("thread-3"); const issued = yield* registry.issue({ threadId, providerInstanceId: ProviderInstanceId.make("claude"), }); const token = issued.config.authorizationHeader.replace(/^Bearer\s+/, ""); // Well past the liveness window in total, but each turn reports in before // it lapses — this is the long-session case that used to lose the toolkit. for (let turn = 0; turn < 10; turn += 1) { timestamp += 99; yield* registry.touch(threadId); } expect((yield* registry.resolve(token))?.threadId).toBe(threadId); }),); it.effect("does not keep credentials of other threads alive", () => Effect.gen(function* () { let timestamp = 1_000; const registry = yield* makeRegistry(() => timestamp); const issued = yield* registry.issue({ threadId: ThreadId.make("thread-4"), providerInstanceId: ProviderInstanceId.make("codex"), }); const token = issued.config.authorizationHeader.replace(/^Bearer\s+/, ""); timestamp += 99; yield* registry.touch(ThreadId.make("thread-unrelated")); timestamp += 2; expect(yield* registry.resolve(token)).toBeUndefined(); }),);
import * as NodeServices from "@effect/platform-node/NodeServices";
import { expect, it } from "@effect/vitest";
import { EnvironmentId, ProviderInstanceId, ThreadId } from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import { HttpServer } from "effect/unstable/http";
import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
import * as McpSessionRegistry from "./McpSessionRegistry.ts";
const environmentId = EnvironmentId.make("environment-1");
const makeFakeHttpServer = (hostname: string, port = 43123) =>
HttpServer.HttpServer.of({
address: { _tag: "TcpAddress", hostname, port },
serve: (() => Effect.void) as HttpServer.HttpServer["Service"]["serve"],
});
const fakeHttpServer = makeFakeHttpServer("127.0.0.1");
const fakeEnvironment = ServerEnvironment.ServerEnvironment.of({
getEnvironmentId: Effect.succeed(environmentId),
getDescriptor: Effect.die("unused"),
});
const makeRegistry = (now: () => number, httpServer = fakeHttpServer) =>
McpSessionRegistry.__testing
.make({
now,
livenessWindowMs: 100,
})
.pipe(
Effect.provideService(HttpServer.HttpServer, httpServer),
Effect.provideService(ServerEnvironment.ServerEnvironment, fakeEnvironment),
Effect.provide(NodeServices.layer),
);
it.effect("stores only a token hash, resolves the bearer token, and revokes by thread", () =>
Effect.gen(function* () {
let timestamp = 1_000;
const registry = yield* makeRegistry(() => timestamp);
const threadId = ThreadId.make("thread-1");
const issued = yield* registry.issue({
threadId,
providerInstanceId: ProviderInstanceId.make("codex"),
});
expect(issued.config.endpoint).toBe("http://127.0.0.1:43123/mcp");
const token = issued.config.authorizationHeader.replace(/^Bearer\s+/, "");
expect(token.length).toBeGreaterThan(20);
const resolved = yield* registry.resolve(token);
expect(resolved?.threadId).toBe(threadId);
yield* registry.revokeThread(threadId);
expect(yield* registry.resolve(token)).toBeUndefined();
timestamp += 2_000;
}),
);
it.effect("builds MCP endpoints from the bound server host", () =>
Effect.gen(function* () {
const cases = [
["100.64.0.40", "http://100.64.0.40:43123/mcp"],
["0.0.0.0", "http://127.0.0.1:43123/mcp"],
["localhost", "http://localhost:43123/mcp"],
["127.0.0.1", "http://127.0.0.1:43123/mcp"],
] as const;
for (const [hostname, expectedEndpoint] of cases) {
const registry = yield* makeRegistry(() => 1_000, makeFakeHttpServer(hostname));
const issued = yield* registry.issue({
threadId: ThreadId.make(`thread-${hostname}`),
providerInstanceId: ProviderInstanceId.make("codex"),
});
expect(issued.config.endpoint).toBe(expectedEndpoint);
}
}),
);
it.effect("expires credentials once their session stops showing signs of life", () =>
Effect.gen(function* () {
let timestamp = 1_000;
const registry = yield* makeRegistry(() => timestamp);
const issued = yield* registry.issue({
threadId: ThreadId.make("thread-2"),
providerInstanceId: ProviderInstanceId.make("claude"),
});
const token = issued.config.authorizationHeader.replace(/^Bearer\s+/, "");
timestamp += 101;
expect(yield* registry.resolve(token)).toBeUndefined();
}),
);
it.effect("keeps a credential alive across turns that never touch an MCP tool", () =>
Effect.gen(function* () {
let timestamp = 1_000;
const registry = yield* makeRegistry(() => timestamp);
const threadId = ThreadId.make("thread-3");
const issued = yield* registry.issue({
threadId,
providerInstanceId: ProviderInstanceId.make("claude"),
});
const token = issued.config.authorizationHeader.replace(/^Bearer\s+/, "");
// Well past the liveness window in total, but each turn reports in before
// it lapses — this is the long-session case that used to lose the toolkit.
for (let turn = 0; turn < 10; turn += 1) {
timestamp += 99;
yield* registry.touch(threadId);
}
expect((yield* registry.resolve(token))?.threadId).toBe(threadId);
}),
);
it.effect("does not keep credentials of other threads alive", () =>
Effect.gen(function* () {
let timestamp = 1_000;
const registry = yield* makeRegistry(() => timestamp);
const issued = yield* registry.issue({
threadId: ThreadId.make("thread-4"),
providerInstanceId: ProviderInstanceId.make("codex"),
});
const token = issued.config.authorizationHeader.replace(/^Bearer\s+/, "");
timestamp += 99;
yield* registry.touch(ThreadId.make("thread-unrelated"));
timestamp += 2;
expect(yield* registry.resolve(token)).toBeUndefined();
}),
);