169 lines · 5.5 KB · TypeScript
RawDownload
import { mkdtempSync } from "node:fs";import { tmpdir } from "node:os";import { join } from "node:path"; import { Effect, Layer } from "effect";import { afterAll, beforeAll, expect, test } from "@effect/vitest"; import { AuthTemplateSlug, ConnectionName, IntegrationSlug } from "@executor-js/sdk";import { makeScopedExecutor } from "@executor-js/api/server"; import { createSelfHostDb, SelfHostDb } from "./db/self-host-db";import { mintInviteCode } from "./testing/mint-invite";import { SelfHostScopedExecutorSeams } from "./execution";import type { SelfHostPlugins } from "./plugins"; // The self-host scoped-executor seams (DbProvider over the long-lived SelfHostDb,// fresh per-request plugins, host config) over the shared `makeScopedExecutor`,// leaving `SelfHostDb` as the only requirement (the production path provides the// same seams via `SelfHostExecutionStackLayer`).const createScopedExecutor = (  accountId: string,  organizationId: string,  organizationName: string,) =>  makeScopedExecutor<SelfHostPlugins>(accountId, organizationId, organizationName).pipe(    Effect.provide(SelfHostScopedExecutorSeams),  ); // End-to-end: an org-owned connection's tools are reachable from a user's MCP// `execute` sandbox.const dataDir = mkdtempSync(join(tmpdir(), "eh-srcmcp-"));const dbPath = join(dataDir, "data.db");process.env.EXECUTOR_DATA_DIR = dataDir;process.env.BETTER_AUTH_SECRET = "srcmcp-secret-0123456789-abcdefghij-klmnop";process.env.EXECUTOR_BOOTSTRAP_ADMIN_EMAIL = "admin@srcmcp.test";process.env.EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD = "admin-pass-123456"; const TINY_SPEC = JSON.stringify({  openapi: "3.0.0",  info: { title: "Tiny", version: "1.0.0" },  servers: [{ url: "https://httpbin.org" }],  paths: {    "/get": {      get: {        operationId: "httpGet",        summary: "Tiny get operation",        responses: { "200": { description: "ok" } },      },    },  },}); let handler!: (request: Request) => Promise<Response>;let dispose: () => Promise<void> = async () => {}; beforeAll(async () => {  const { makeSelfHostApiHandler } = await import("./app");  const app = await makeSelfHostApiHandler({ dbPath });  handler = app.handler;  dispose = app.dispose;});afterAll(() => dispose()); const BASE = "http://localhost:4788"; const addOrgIntegration = async (organizationId: string): Promise<void> => {  // Register the integration and attach an org-owned connection, on its own  // connection to the shared DB file. WAL makes the committed rows visible to  // the server. Org-owned connections (and their per-connection tools) are  // shared across every member of the tenant.  const seedDb = await createSelfHostDb({    path: dbPath,    namespace: "executor_selfhost",    version: "1.0.0",  });  await Effect.runPromise(    Effect.gen(function* () {      const admin = yield* createScopedExecutor("seed", organizationId, "Default");      yield* admin.openapi.addSpec({        spec: { kind: "blob", value: TINY_SPEC },        slug: "tiny",        baseUrl: "",      });      yield* admin.connections.create({        owner: "org",        name: ConnectionName.make("shared"),        integration: IntegrationSlug.make("tiny"),        template: AuthTemplateSlug.make("none"),        value: "",      });    }).pipe(Effect.provide(Layer.succeed(SelfHostDb)(seedDb)), Effect.scoped),  );  await seedDb.close();}; test("a user's MCP execute sandbox can reach an org-owned connection's tools", async () => {  const inviteCode = await mintInviteCode(handler);  const su = await handler(    new Request(`${BASE}/api/auth/sign-up/email`, {      method: "POST",      headers: { "content-type": "application/json" },      body: JSON.stringify({        email: "u@srcmcp.test",        password: "password-12345678",        name: "U",        inviteCode,      }),    }),  );  const token = su.headers.get("set-auth-token") ?? "";  expect(token).not.toBe("");   // The user's real org id (Better Auth assigns a random org id) — the tenant the  // per-request executor binds to.  const meRes = await handler(    new Request(`${BASE}/api/account/me`, {      headers: { authorization: `Bearer ${token}` },    }),  );  const organizationId = ((await meRes.json()) as { organization: { id: string } }).organization.id;   await addOrgIntegration(organizationId);   const mcp = (body: unknown, sessionId?: string) =>    handler(      new Request(`${BASE}/mcp`, {        method: "POST",        headers: {          authorization: `Bearer ${token}`,          "content-type": "application/json",          accept: "application/json, text/event-stream",          ...(sessionId ? { "mcp-session-id": sessionId } : {}),        },        body: JSON.stringify(body),      }),    );   const init = await mcp({    jsonrpc: "2.0",    id: 1,    method: "initialize",    params: {      protocolVersion: "2025-03-26",      capabilities: {},      clientInfo: { name: "t", version: "1" },    },  });  const sessionId = init.headers.get("mcp-session-id") ?? "";  expect(sessionId).not.toBe("");  await init.text();  await mcp({ jsonrpc: "2.0", method: "notifications/initialized" }, sessionId);   const call = await mcp(    {      jsonrpc: "2.0",      id: 3,      method: "tools/call",      params: {        name: "execute",        arguments: {          code: 'export default (await tools.search({ query: "tiny get operation", limit: 10 })).items.map((m) => m.path)',        },      },    },    sessionId,  );  expect(call.status).toBe(200);  expect(JSON.stringify(await call.json())).toContain("tiny");});