import { afterAll, test } from "bun:test"; import assert from "node:assert/strict"; import { mkdirSync, mkdtempSync, rmSync, statSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { openLocalStore } from "../../src/runtime/bun/mcp-stdio"; import { serve } from "../../src/runtime/bun/server"; import { openDatabase } from "../../src/runtime/bun/sqlite"; const root = mkdtempSync(join(tmpdir(), "titen-local-mode-")); const cli = join(import.meta.dir, "../../src/runtime/bun/cli.ts"); /** * Zero-config local mode claims it never reaches the network. Preloading a * throwing `fetch` turns that claim into something the run can fail on: the * bridge path would call it, the in-process path must not. */ const preload = join(root, "no-network.ts"); writeFileSync(preload, ` globalThis.fetch = () => { throw new Error("local mode attempted a network call"); }; `); afterAll(() => rmSync(root, { recursive: true, force: true })); const initialize = { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "local-mode-test", version: "1" }, }, }; const initialized = { jsonrpc: "2.0", method: "notifications/initialized" }; const call = (id: number, name: string, args: Record) => ({ jsonrpc: "2.0", id, method: "tools/call", params: { name, arguments: args }, }); /** Drives the installed entry point over stdio with no Titen environment at all. */ async function stdio(home: string, messages: unknown[], env: Record = {}) { const child = Bun.spawn({ cmd: ["bun", "--preload", preload, cli, "mcp"], // Deliberately not `...process.env`: this is the no-configuration case. env: { PATH: process.env.PATH ?? "", HOME: home, ...env }, stdin: "pipe", stdout: "pipe", stderr: "pipe", }); child.stdin.write(messages.map((message) => `${JSON.stringify(message)}\n`).join("")); child.stdin.end(); const [exitCode, stdout, stderr] = await Promise.all([ Promise.race([child.exited, Bun.sleep(20_000).then(() => -1)]), new Response(child.stdout).text(), new Response(child.stderr).text(), ]); const lines = stdout.split("\n").filter((line) => line.trim() !== ""); return { exitCode, stderr, stdout, replies: lines.map((line) => JSON.parse(line)) }; } const toolResult = (reply: any) => JSON.parse(reply.result.content[0].text); test("with no environment set, the stdio entry point serves memory from ~/.titen", async () => { const home = join(root, "fresh"); mkdirSync(home); const statement = "Titen local mode needs no API key and no server."; const first = await stdio(home, [ initialize, initialized, { jsonrpc: "2.0", id: 2, method: "tools/list" }, call(3, "titen_project_resolve", { reference: "local" }), call(4, "titen_remember", { subject_id: "local-mode", kind: "user_statement", content: statement, source_type: "chat", source_ref: "local-mode-test#1", // Above the default, so it passes assertTrustCeiling only because the // provisioned owner is a real credential row carrying a real ceiling. // (`policy_approved` is reserved for the claim-approval workflow.) trust: "verified", }), ]); assert.equal(first.exitCode, 0, first.stderr); // Which store answered is exactly what a silent fallback hides. A client // config that shadows the bridge entry with an env-less one sends `titen mcp` // here instead of to the served instance; every lookup then truthfully // returns nothing, and without this line neither stream says why. assert.equal( first.stderr, "titen: no TITEN_MCP_URL/TITEN_API_KEY set; serving the local store " + `${join(home, ".titen", "memory.db")}\n`, "local mode names the store it opened, and says nothing else", ); assert.equal(first.replies.length, 4, "the notification gets no reply"); const [handshake, tools, project, remembered] = first.replies; assert.equal(handshake.result.serverInfo.name, "titen"); assert.equal(handshake.result.protocolVersion, "2025-06-18"); // stderr reaches a host log file; `instructions` reaches the model, which is // the reader who would otherwise take an empty pack for an empty memory. assert.ok( handshake.result.instructions.includes(join(home, ".titen", "memory.db")), "the handshake names the store, in band", ); assert.match(handshake.result.instructions, /neither TITEN_MCP_URL nor TITEN_API_KEY was set/); assert.ok( tools.result.tools.some((tool: { name: string }) => tool.name === "titen_remember"), "the same MCP tool surface is served locally", ); // The provisioned project resolves without `create`, so it is a real row. assert.equal(toolResult(project).data.project_id, "project_local"); assert.notEqual(remembered.result.isError, true, remembered.result.content?.[0]?.text); const observation = toolResult(remembered).data; assert.match(observation.observation_id, /^obs_/); assert.equal(observation.trust, "verified"); const observationId = observation.observation_id; // A second process proves the store persists and that startup is idempotent. const second = await stdio(home, [ initialize, initialized, call(2, "titen_consolidate", { subject_id: "local-mode", claims: [{ kind: "semantic_fact", statement, confidence: 0.9, sources: [{ observation_id: observationId, relation: "supports" }], }], }), call(3, "titen_compile", { subject_id: "local-mode", task: "does local mode need an API key", max_tokens: 500, }), ]); assert.equal(second.exitCode, 0, second.stderr); const [, consolidated, compiled] = second.replies; assert.notEqual(consolidated.result.isError, true, consolidated.result.content?.[0]?.text); const pack = toolResult(compiled); assert.deepEqual(pack.data.items.map((item: { claim: string }) => item.claim), [statement]); // FTS only: no embedding provider is configured, so nothing can call out. assert.equal(pack.meta.degraded.vector, "disabled"); assert.equal(pack.meta.degraded.embedding, "disabled"); assert.equal(pack.meta.degraded.lexical, "used"); }, 60_000); test("the local store holds real organization, workspace, project and owner rows", async () => { const home = join(root, "records"); mkdirSync(home); await stdio(home, [initialize]); await stdio(home, [initialize]); const dbPath = join(home, ".titen", "memory.db"); assert.equal(statSync(dbPath).mode & 0o777, 0o600, "the store is not world readable"); assert.equal(statSync(`${dbPath}.key`).mode & 0o777, 0o600, "the owner key is not world readable"); const database = openDatabase(dbPath, { create: false, readonly: true }); try { const rows = (sql: string) => database.query(sql).all() as Record[]; assert.deepEqual( rows("SELECT id, name FROM organizations"), [{ id: "org_local", name: "Local" }], "a second run adopts the first run's organization instead of adding another", ); assert.deepEqual(rows("SELECT id, name FROM workspaces"), [{ id: "ws_local", name: "Local" }]); assert.deepEqual( rows("SELECT id, reference FROM projects"), [{ id: "project_local", reference: "local" }], ); assert.deepEqual( rows(`SELECT principal_id, role, workspace_id FROM memberships ORDER BY workspace_id`), [ { principal_id: "owner", role: "owner", workspace_id: null as unknown as string }, { principal_id: "owner", role: "owner", workspace_id: "ws_local" }, ], ); assert.deepEqual(rows("SELECT principal_id, username FROM operator_accounts"), [ { principal_id: "owner", username: "owner" }, ]); // One reusable credential row, not one per process: the key file is reused. assert.deepEqual( rows(`SELECT org_id, principal_id, scopes, max_trust, revoked_at FROM api_keys`), [{ org_id: "org_local", principal_id: "owner", scopes: "*", max_trust: "policy_approved", revoked_at: null as unknown as string, }], ); } finally { database.close(); } }, 60_000); test("local mode authenticates for real and never downgrades a partial configuration", async () => { const dbPath = join(root, "predicates", "memory.db"); const local = await openLocalStore(dbPath); try { const mcp = (headers: Record) => local.app(new Request("http://127.0.0.1/mcp", { method: "POST", headers: { "content-type": "application/json", ...headers }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }), })); assert.equal((await mcp({})).status, 401, "the in-process app still requires a credential"); assert.equal( (await mcp({ authorization: "Bearer titen_sk_not-a-real-key" })).status, 401, "an unissued key is rejected by the same core predicate", ); assert.equal((await mcp({ authorization: `Bearer ${local.apiKey}` })).status, 200); } finally { local.close(); } // Half a configuration is a mistake, not an invitation to open a local store. const partial = await stdio(join(root, "predicates"), [initialize], { TITEN_MCP_URL: "http://127.0.0.1:8787/mcp", }); assert.notEqual(partial.exitCode, 0); assert.match(partial.stderr, /TITEN_MCP_URL and TITEN_API_KEY/); assert.equal(partial.replies.length, 0); }, 60_000); test("the served entry point still rejects an unauthenticated request", async () => { const running = await serve({ dbPath: join(root, "served.db"), port: 0, hostname: "127.0.0.1", quiet: true, maintenanceIntervalMs: 0, }); try { const response = await fetch(`${running.url}/mcp`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }), }); assert.equal(response.status, 401); } finally { await running.stop(); } });