fix(cli): various mcp server fixes (#2430)

* fix: span events are optional, as logs don't have them

* remove CI: true env var to prevent massive tool responses

* fix github resolver relative path

* prevent the dev command from asking for user input when not interactive

* Remove lodash.get because it's deprecated

* span output can be object other than an object, like a string

* prevent large traces from causing get_run_details failures

* Decreased max log lines, made it a tool input property

* Some install mcp tweaks
This commit is contained in:
Eric Allam
2025-08-21 13:57:14 +01:00
committed by GitHub
parent 58ff034325
commit 895e24a0d7
13 changed files with 89 additions and 67 deletions
+1
View File
@@ -436,6 +436,7 @@ const EnvironmentSchema = z.object({
EVENT_LOOP_MONITOR_ENABLED: z.string().default("1"),
MAXIMUM_LIVE_RELOADING_EVENTS: z.coerce.number().int().default(1000),
MAXIMUM_TRACE_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(25_000),
MAXIMUM_TRACE_DETAILED_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(10_000),
TASK_PAYLOAD_OFFLOAD_THRESHOLD: z.coerce.number().int().default(524_288), // 512KB
TASK_PAYLOAD_MAXIMUM_SIZE: z.coerce.number().int().default(3_145_728), // 3MB
BATCH_TASK_PAYLOAD_MAXIMUM_SIZE: z.coerce.number().int().default(1_000_000), // 1MB
+2 -2
View File
@@ -285,7 +285,7 @@ export class TaskEventStore {
: Prisma.empty
}
ORDER BY "startTime" ASC
LIMIT ${env.MAXIMUM_TRACE_SUMMARY_VIEW_COUNT}
LIMIT ${env.MAXIMUM_TRACE_DETAILED_SUMMARY_VIEW_COUNT}
`;
} else {
return await this.readReplica.$queryRaw<DetailedTraceEvent[]>`
@@ -320,7 +320,7 @@ export class TaskEventStore {
: Prisma.empty
}
ORDER BY "startTime" ASC
LIMIT ${env.MAXIMUM_TRACE_SUMMARY_VIEW_COUNT}
LIMIT ${env.MAXIMUM_TRACE_DETAILED_SUMMARY_VIEW_COUNT}
`;
}
}
+32 -29
View File
@@ -107,47 +107,50 @@ export function configureDevCommand(program: Command) {
export async function devCommand(options: DevCommandOptions) {
runtimeChecks();
const skipMCPInstall = typeof options.skipMCPInstall === "boolean" && options.skipMCPInstall;
// Only show these install prompts if the user is in a terminal (not in a Coding Agent)
if (process.stdout.isTTY) {
const skipMCPInstall = typeof options.skipMCPInstall === "boolean" && options.skipMCPInstall;
if (!skipMCPInstall) {
const hasSeenMCPInstallPrompt = readConfigHasSeenMCPInstallPrompt();
if (!skipMCPInstall) {
const hasSeenMCPInstallPrompt = readConfigHasSeenMCPInstallPrompt();
if (!hasSeenMCPInstallPrompt) {
const installChoice = await confirm({
message: "Would you like to install the Trigger.dev MCP server?",
initialValue: true,
});
if (!hasSeenMCPInstallPrompt) {
const installChoice = await confirm({
message: "Would you like to install the Trigger.dev MCP server?",
initialValue: true,
});
writeConfigHasSeenMCPInstallPrompt(true);
writeConfigHasSeenMCPInstallPrompt(true);
const skipInstall = isCancel(installChoice) || !installChoice;
const skipInstall = isCancel(installChoice) || !installChoice;
if (!skipInstall) {
log.step("Welcome to the Trigger.dev MCP server install wizard 🧙");
if (!skipInstall) {
log.step("Welcome to the Trigger.dev MCP server install wizard 🧙");
const [installError] = await tryCatch(
installMcpServer({
yolo: false,
tag: VERSION as string,
logLevel: options.logLevel,
})
);
const [installError] = await tryCatch(
installMcpServer({
yolo: false,
tag: VERSION as string,
logLevel: options.logLevel,
})
);
if (installError) {
log.error(`Failed to install MCP server: ${installError.message}`);
if (installError) {
log.error(`Failed to install MCP server: ${installError.message}`);
}
}
}
}
}
const skipRulesInstall =
typeof options.skipRulesInstall === "boolean" && options.skipRulesInstall;
const skipRulesInstall =
typeof options.skipRulesInstall === "boolean" && options.skipRulesInstall;
if (!skipRulesInstall) {
await initiateRulesInstallWizard({
manifestPath: options.rulesInstallManifestPath,
branch: options.rulesInstallBranch,
});
if (!skipRulesInstall) {
await initiateRulesInstallWizard({
manifestPath: options.rulesInstallManifestPath,
branch: options.rulesInstallBranch,
});
}
}
const authorization = await login({
+7 -11
View File
@@ -233,12 +233,12 @@ export async function installMcpServer(
);
log.info("More examples:");
log.message(`${chalk.green('"List my Trigger.dev projects"')}`);
log.message(`${chalk.green('"Create a new Trigger.dev project called MyApp"')}`);
log.message(`${chalk.green('"Show me all tasks in my project"')}`);
log.message(`${chalk.green('"Trigger the email-notification task"')}`);
log.message(`${chalk.green('"Trigger the hello-world task"')}`);
log.message(`${chalk.green('"Can you help me debug the prod run run_1234"')}`);
log.message(`${chalk.green('"Deploy my trigger project to staging"')}`);
log.message(`${chalk.green('"What trigger task handles uploading files to S3?"')}`);
log.message(`${chalk.green('"How do I create a scheduled task in Trigger.dev?"')}`);
log.message(`${chalk.green('"Search Trigger.dev docs for webhook examples"')}`);
log.message(`${chalk.green('"Search Trigger.dev docs for ffmpeg examples"')}`);
log.info("Helpful links:");
log.message(`${cliLink("Trigger.dev docs", "https://trigger.dev/docs")}`);
@@ -318,17 +318,13 @@ async function installMcpServerForClient(
return;
}
const clientSpinner = spinner();
clientSpinner.start(`Installing in ${clientName}`);
const scope = await resolveScopeForClient(clientName, options);
clientSpinner.message(`Installing in ${scope.scope} scope at ${scope.location}`);
// clientSpinner.message(`Installing in ${scope.scope} scope at ${scope.location}`);
const configPath = await performInstallForClient(clientName, scope, options);
clientSpinner.stop(`Successfully installed in ${clientName} (${configPath})`);
// clientSpinner.stop(`Successfully installed in ${clientName} (${configPath})`);
return { configPath, clientName, scope };
}
+28 -8
View File
@@ -5,6 +5,8 @@ import {
} from "@trigger.dev/core/v3/schemas";
import type { CursorPageResponse } from "@trigger.dev/core/v3/zodfetch";
const DEFAULT_MAX_TRACE_LINES = 500;
export function formatRun(run: RetrieveRunResponse): string {
const lines: string[] = [];
@@ -170,14 +172,21 @@ function formatRelatedRuns(relatedRuns: RetrieveRunResponse["relatedRuns"]): str
return parts.length > 0 ? `Related: ${parts.join("; ")}` : null;
}
export function formatRunTrace(trace: RetrieveRunTraceResponseBody["trace"]): string {
export function formatRunTrace(
trace: RetrieveRunTraceResponseBody["trace"],
maxTraceLines: number = DEFAULT_MAX_TRACE_LINES
): string {
const lines: string[] = [];
lines.push(`Trace ID: ${trace.traceId}`);
lines.push("");
// Format the root span and its children recursively
formatSpan(trace.rootSpan, lines, 0);
const reachedMaxLines = formatSpan(trace.rootSpan, lines, 0, maxTraceLines);
if (reachedMaxLines) {
lines.push(`(truncated logs to ${maxTraceLines} lines)`);
}
return lines.join("\n");
}
@@ -185,8 +194,13 @@ export function formatRunTrace(trace: RetrieveRunTraceResponseBody["trace"]): st
function formatSpan(
span: RetrieveRunTraceResponseBody["trace"]["rootSpan"],
lines: string[],
depth: number
): void {
depth: number,
maxLines: number
): boolean {
if (lines.length >= maxLines) {
return true;
}
const indent = " ".repeat(depth);
const prefix = depth === 0 ? "└─" : "├─";
@@ -230,7 +244,7 @@ function formatSpan(
}
// Show output if it exists
if (span.data.output && Object.keys(span.data.output).length > 0) {
if (span.data.output) {
lines.push(
`${indent} Output: ${JSON.stringify(span.data.output, null, 2).replace(
/\n/g,
@@ -263,14 +277,20 @@ function formatSpan(
// Recursively format children
if (span.children) {
span.children.forEach((child, index) => {
formatSpan(child, lines, depth + 1);
const reachedMaxLines = span.children.some((child, index) => {
const reachedMaxLines = formatSpan(child, lines, depth + 1, maxLines);
// Add spacing between sibling spans (except for the last one)
if (index < span.children.length - 1) {
if (index < span.children.length - 1 && !reachedMaxLines) {
lines.push("");
}
return reachedMaxLines;
});
return reachedMaxLines;
}
return false;
}
function getStatusIndicator(
+7 -1
View File
@@ -123,7 +123,13 @@ export const CommonRunsInput = CommonProjectsInput.extend({
export type CommonRunsInput = z.output<typeof CommonRunsInput>;
export const GetRunDetailsInput = CommonRunsInput.extend({});
export const GetRunDetailsInput = CommonRunsInput.extend({
maxTraceLines: z
.number()
.int()
.describe("The maximum number of lines to show in the trace. Defaults to 500")
.optional(),
});
export type GetRunDetailsInput = z.output<typeof GetRunDetailsInput>;
-1
View File
@@ -74,7 +74,6 @@ export const deployTool = {
cwd: cwd.cwd,
env: {
TRIGGER_MCP_SERVER: "1",
CI: "true",
},
},
});
+1 -1
View File
@@ -35,7 +35,7 @@ export const getRunDetailsTool = {
]);
const formattedRun = formatRun(runResult);
const formattedTrace = formatRunTrace(traceResult.trace);
const formattedTrace = formatRunTrace(traceResult.trace, input.maxTraceLines);
const runUrl = await ctx.getDashboardUrl(`/projects/v3/${projectRef}/runs/${runResult.id}`);
+1 -1
View File
@@ -126,7 +126,7 @@ export class GithubRulesManifestLoader implements RulesManifestLoader {
async loadRulesFile(relativePath: string): Promise<string> {
const response = await fetch(
`https://raw.githubusercontent.com/triggerdotdev/trigger.dev/refs/heads/${this.branch}/${relativePath}`
`https://raw.githubusercontent.com/triggerdotdev/trigger.dev/refs/heads/${this.branch}/rules/${relativePath}`
);
if (!response.ok) {
-1
View File
@@ -187,7 +187,6 @@
"execa": "^8.0.1",
"humanize-duration": "^3.27.3",
"jose": "^5.4.0",
"lodash.get": "^4.4.2",
"nanoid": "3.3.8",
"prom-client": "^15.1.0",
"socket.io": "4.7.4",
+2 -2
View File
@@ -1233,7 +1233,7 @@ export const RetrieveRunTraceSpanSchema = z.object({
runId: z.string(),
taskSlug: z.string().optional(),
taskPath: z.string().optional(),
events: z.array(z.any()),
events: z.array(z.any()).optional(),
startTime: z.coerce.date(),
duration: z.number(),
isError: z.boolean(),
@@ -1245,7 +1245,7 @@ export const RetrieveRunTraceSpanSchema = z.object({
queueName: z.string().optional(),
machinePreset: z.string().optional(),
properties: z.record(z.any()).optional(),
output: z.record(z.any()).optional(),
output: z.unknown().optional(),
}),
});
@@ -12,7 +12,7 @@ import { SemanticInternalAttributes } from "../semanticInternalAttributes.js";
import { TriggerTracer } from "../tracer.js";
import { zodfetch } from "../zodfetch.js";
import { flattenAttributes } from "./flattenAttributes.js";
import get from "lodash.get";
import { JSONHeroPath } from "@jsonhero/path";
export type IOPacket = {
data?: string | undefined;
@@ -536,7 +536,7 @@ export async function replaceSuperJsonPayload(original: string, newPayload: stri
.map(([key]) => key);
const overridenUndefinedKeys = originalUndefinedKeys.filter(
(key) => get(newPayloadObject, key) !== undefined
(key) => getKeyFromObject(newPayloadObject, key) !== undefined
);
overridenUndefinedKeys.forEach((key) => {
@@ -551,3 +551,9 @@ export async function replaceSuperJsonPayload(original: string, newPayload: stri
return superjson.deserialize(newSuperJson);
}
function getKeyFromObject(object: unknown, key: string) {
const jsonHeroPath = new JSONHeroPath(key);
return jsonHeroPath.first(object);
}
-8
View File
@@ -1576,9 +1576,6 @@ importers:
jose:
specifier: ^5.4.0
version: 5.4.0
lodash.get:
specifier: ^4.4.2
version: 4.4.2
nanoid:
specifier: 3.3.8
version: 3.3.8
@@ -28730,11 +28727,6 @@ packages:
resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==}
dev: true
/lodash.get@4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
dev: false
/lodash.groupby@4.6.0:
resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==}
dev: false