UNPKG

trigger.dev

Version:

A Command-Line Interface for Trigger.dev projects

191 lines 7.03 kB
import { tryCatch } from "@trigger.dev/core/utils"; import { ApiClient } from "@trigger.dev/core/v3"; import fs from "node:fs"; import path from "node:path"; import { CliApiClient } from "../apiClient.js"; import { loadConfig } from "../config.js"; import { mcpAuth } from "./auth.js"; import { hasElicitationCapability, hasRootsCapability, hasSamplingCapability, } from "./capabilities.js"; import { fileURLToPath } from "node:url"; const MCP_CONFIG_DIR = ".trigger"; const MCP_CONFIG_FILE = "mcp.json"; function readMcpProjectConfig(projectDir) { try { const filePath = path.join(projectDir, MCP_CONFIG_DIR, MCP_CONFIG_FILE); const content = fs.readFileSync(filePath, "utf-8"); return JSON.parse(content); } catch { return undefined; } } function writeMcpProjectConfig(projectDir, config) { const dir = path.join(projectDir, MCP_CONFIG_DIR); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(path.join(dir, MCP_CONFIG_FILE), JSON.stringify(config, null, 2) + "\n"); } export class McpContext { server; options; _profileLoaded; _resolveProfileLoaded; constructor(server, options) { this.server = server; this.options = options; this._profileLoaded = new Promise((resolve) => { this._resolveProfileLoaded = resolve; }); } get logger() { return this.options.fileLogger; } async getAuth() { // Wait for project profile to be loaded before authenticating await this._profileLoaded; const auth = await mcpAuth({ server: this.server, defaultApiUrl: this.options.apiUrl, profile: this.options.profile, context: this, }); if (!auth.ok) { throw new Error(auth.error); } return auth; } async getCliApiClient(branch) { const auth = await this.getAuth(); return new CliApiClient(auth.auth.apiUrl, auth.auth.accessToken, branch); } async getApiClient(options) { const cliApiClient = await this.getCliApiClient(options.branch); const jwt = await cliApiClient.getJWT(options.projectRef, options.environment, { claims: { scopes: options.scopes, }, }); if (!jwt.success) { throw new Error(`Could not get the authentication token for the project ${options.projectRef} in the ${options.environment} environment. Please try again.`); } return new ApiClient(cliApiClient.apiURL, jwt.data.token, undefined, { additionalHeaders: { "x-trigger-source": "mcp" }, }); } async getCwd() { if (!this.hasRootsCapability) { return undefined; } const response = await this.server.server.listRoots(); if (response.roots.length >= 1) { return response.roots[0]?.uri ? fileURLToPath(response.roots[0].uri) : undefined; } return undefined; } async getProjectRef(options) { if (options.projectRef) { return options.projectRef; } const projectDir = await this.getProjectDir({ cwd: options.cwd }); if (!projectDir.ok) { throw new Error(projectDir.error); } const [_, config] = await tryCatch(loadConfig({ cwd: projectDir.cwd })); if (config?.configFile && typeof config.project === "string" && config.project.startsWith("proj_")) { return config.project; } throw new Error("No project ref found. Please provide a projectRef."); } async getProjectDir({ cwd }) { // If cwd is a path to the actual trigger.config.ts file, then we should set the cwd to the directory of the file let $cwd = cwd ? (path.extname(cwd) !== "" ? path.dirname(cwd) : cwd) : undefined; function isRelativePath(filePath) { return !path.isAbsolute(filePath); } if (!cwd) { if (!this.hasRootsCapability) { return { ok: false, error: "The current MCP server does not support the roots capability, so please call the tool again with a projectRef or an absolute path as cwd parameter", }; } $cwd = await this.getCwd(); } else if (isRelativePath(cwd)) { if (!this.hasRootsCapability) { return { ok: false, error: "The current MCP server does not support the roots capability, so please call the tool again with a projectRef or an absolute path as cwd parameter", }; } const resolvedCwd = await this.getCwd(); if (!resolvedCwd) { return { ok: false, error: "No current working directory found. Please provide a projectRef or a cwd.", }; } $cwd = path.resolve(resolvedCwd, cwd); } if (!$cwd) { return { ok: false, error: "No current working directory found. Please provide a projectRef or a cwd.", }; } return { ok: true, cwd: $cwd, }; } switchProfile(profile, projectDir) { this.options.profile = profile; // Persist to project-scoped config if we can resolve the project dir if (projectDir) { try { const existing = readMcpProjectConfig(projectDir) ?? {}; writeMcpProjectConfig(projectDir, { ...existing, profile }); } catch { // Non-fatal — profile still switched in memory } } } /** * Load the persisted profile from the project-scoped .trigger/mcp.json. * Overrides the default global profile with the project-scoped one. * Must be called once after initialization — tools wait for this before authenticating. */ async loadProjectProfile() { try { const cwd = await this.getCwd(); if (!cwd) return; const config = readMcpProjectConfig(cwd); if (config?.profile) { this.options.profile = config.profile; this.logger?.log("Loaded project profile", { profile: config.profile, cwd }); } } finally { this._resolveProfileLoaded?.(); } } async getDashboardUrl(path) { const auth = await this.getAuth(); return `${auth.dashboardUrl}${path}`; } get hasRootsCapability() { return hasRootsCapability(this.server); } get hasSamplingCapability() { return hasSamplingCapability(this.server); } get hasElicitationCapability() { return hasElicitationCapability(this.server); } } //# sourceMappingURL=context.js.map