Skip to content

Commit 9d38ff5

Browse files
isshaddadTrigger.dev RepoOps
authored andcommitted
feat(cli,webapp): add a submit_feedback MCP tool
Adds a `submit_feedback` MCP tool. Coding agents hit confusing tool errors, docs that disagree with actual behaviour, and missing capabilities, then work around them silently — none of which we ever see. The tool lets the agent report the problem itself, without the user having to file it by hand. `submit_feedback` takes a `message` (what was attempted, what happened, what was expected) and an optional `toolName` and `projectRef`. The CLI version is attached automatically. The tool description tells the agent to describe the problem in its own words rather than pasting raw output, to leave out secrets, credentials, environment variables and the user's own data, to tell the user what it reported, and not to file the same problem twice. Reports go straight from the CLI to analytics, attributed to the signed-in user. Reporting is off when `--skip-telemetry` is passed or `TRIGGER_TELEMETRY_DISABLED` is set, and while it is off the tool is hidden rather than left to fail. Mono-RevId: 8ec525a24ad2049ec7bdb0f13c156c12c763e411
1 parent 84ec665 commit 9d38ff5

10 files changed

Lines changed: 170 additions & 1 deletion

File tree

‎.changeset/mcp-submit-feedback.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
Added a `submit_feedback` MCP tool so coding agents can report a confusing tool error, a docs mismatch, or a missing capability without the user having to file it by hand. Turn it off with `--skip-telemetry` or `TRIGGER_TELEMETRY_DISABLED`; the tool is hidden while it is off.

‎packages/cli-v3/package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"mcp:smoke": "tsx src/mcp/smoke.test.ts"
7777
},
7878
"dependencies": {
79+
"@babel/parser": "^7.29.7",
7980
"@clack/prompts": "0.11.0",
8081
"@depot/cli": "0.0.1-cli.2.80.0",
8182
"@modelcontextprotocol/sdk": "^1.25.2",
@@ -107,7 +108,6 @@
107108
"ini": "^5.0.0",
108109
"json-stable-stringify": "^1.3.0",
109110
"jsonc-parser": "3.2.1",
110-
"@babel/parser": "^7.29.7",
111111
"magicast": "^0.3.4",
112112
"minimatch": "^10.0.1",
113113
"mlly": "^1.7.1",
@@ -116,6 +116,7 @@
116116
"p-limit": "^6.2.0",
117117
"p-retry": "^6.1.0",
118118
"pkg-types": "^1.1.3",
119+
"posthog-node": "5.35.6",
119120
"resolve": "^1.22.8",
120121
"semver": "^7.5.0",
121122
"signal-exit": "^4.1.0",

‎packages/cli-v3/src/consts.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
export const COMMAND_NAME = "trigger.dev";
22
export const CLOUD_WEB_URL = "https://cloud.trigger.dev";
33
export const CLOUD_API_URL = "https://api.trigger.dev";
4+
5+
// Write-only project key, the same one the dashboard already ships to every browser.
6+
export const POSTHOG_PROJECT_KEY = "phc_LFH7kJiGhdIlnO22hTAKgHpaKhpM8gkzWAFvHmf5vfS";
7+
export const POSTHOG_INGEST_HOST = "https://eu.i.posthog.com";

‎packages/cli-v3/src/mcp/config.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ export const toolsMetadata = {
1616
description:
1717
"Search across the Trigger.dev documentation to find relevant information, code examples, API references, and guides. Use this tool when you need to answer questions about Trigger.dev, find specific documentation, understand how features work, or locate implementation details. The search returns contextual content with titles and direct links to the documentation pages",
1818
},
19+
submit_feedback: {
20+
name: "submit_feedback",
21+
title: "Submit Feedback",
22+
description:
23+
"Report a problem with the Trigger.dev MCP server, SDK, or documentation. Use this when a tool returned a confusing error, the docs disagreed with the actual behaviour, a capability you needed was missing, or you had to work around something to finish the user's task. Describe what you were trying to do, what happened, and what you expected instead, in your own words. Never include secrets, credentials, environment variables, tokens or the user's own data - summarise and redact instead of pasting raw output. Tell the user what you reported. Do not call this more than once for the same problem.",
24+
},
1925
list_projects: {
2026
name: "list_projects",
2127
title: "List Projects",

‎packages/cli-v3/src/mcp/context.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export type McpContextOptions = {
4646
profile?: string;
4747
devOnly?: boolean;
4848
readonly?: boolean;
49+
skipTelemetry?: boolean;
4950
};
5051

5152
export class McpContext {

‎packages/cli-v3/src/mcp/contextOptions.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ export function toMcpContextOptions(
2222
profile: options.profile,
2323
readonly: options.readonly,
2424
devOnly: options.devOnly,
25+
skipTelemetry: options.skipTelemetry,
2526
};
2627
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { PostHog } from "posthog-node";
2+
import { env } from "std-env";
3+
import { POSTHOG_INGEST_HOST, POSTHOG_PROJECT_KEY } from "../consts.js";
4+
import type { McpContextOptions } from "./context.js";
5+
6+
/**
7+
* Analytics for the MCP server, sent straight from the CLI.
8+
*
9+
* Off when the user says so, by `--skip-telemetry` or TRIGGER_TELEMETRY_DISABLED (the same
10+
* variable the webapp honours). Tools that report anywhere are hidden while it is off, rather
11+
* than left to fail at call time.
12+
*/
13+
export function isTelemetryEnabled(options: McpContextOptions): boolean {
14+
if (options.skipTelemetry) {
15+
return false;
16+
}
17+
18+
return env.TRIGGER_TELEMETRY_DISABLED === undefined;
19+
}
20+
21+
export type FeedbackEvent = {
22+
userId: string;
23+
message: string;
24+
toolName?: string;
25+
projectRef?: string;
26+
cliVersion: string;
27+
};
28+
29+
/**
30+
* Captures a feedback report and waits for it to leave the machine. A stdio MCP server dies
31+
* whenever its client closes it, so an unflushed report would be lost while the agent had
32+
* already been told it was sent.
33+
*/
34+
export async function captureFeedback(event: FeedbackEvent): Promise<void> {
35+
// A client per report, shut down straight after. flush() resolves without delivering
36+
// anything - only shutdown() drains the queue - and reports are rare enough that the
37+
// extra client costs nothing next to reporting success for something never sent.
38+
const posthog = new PostHog(POSTHOG_PROJECT_KEY, { host: POSTHOG_INGEST_HOST });
39+
40+
// shutdown() swallows fetch failures - it logs them and resolves - so without this the tool
41+
// would report a delivery that never happened. The error event is the only signal.
42+
let deliveryError: Error | undefined;
43+
posthog.on("error", (error: unknown) => {
44+
deliveryError ??= error instanceof Error ? error : new Error(String(error));
45+
});
46+
47+
posthog.capture({
48+
distinctId: event.userId,
49+
event: "mcp_feedback_submitted",
50+
properties: {
51+
message: event.message,
52+
toolName: event.toolName,
53+
projectRef: event.projectRef,
54+
cliVersion: event.cliVersion,
55+
source: "mcp",
56+
},
57+
groups: event.projectRef ? { project: event.projectRef } : undefined,
58+
});
59+
60+
await posthog.shutdown();
61+
62+
if (deliveryError) {
63+
throw deliveryError;
64+
}
65+
}

‎packages/cli-v3/src/mcp/tools.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { McpContext } from "./context.js";
22
import { deployTool, listDeploysTool } from "./tools/deploys.js";
33
import { searchDocsTool } from "./tools/docs.js";
4+
import { submitFeedbackTool } from "./tools/feedback.js";
45
import {
56
createProjectInOrgTool,
67
initializeProjectTool,
@@ -34,6 +35,7 @@ import { listAgentsTool } from "./tools/agents.js";
3435
import { startAgentChatTool, sendAgentMessageTool, closeAgentChatTool } from "./tools/agentChat.js";
3536
import { readSessionChannelTool, writeSessionChannelTool } from "./tools/sessionChannels.js";
3637
import { respondWithError } from "./utils.js";
38+
import { isTelemetryEnabled } from "./telemetry.js";
3739

3840
/** Tool names that perform write/mutating operations. */
3941
const WRITE_TOOLS = new Set([
@@ -51,11 +53,13 @@ const WRITE_TOOLS = new Set([
5153
sendAgentMessageTool.name,
5254
closeAgentChatTool.name,
5355
writeSessionChannelTool.name,
56+
submitFeedbackTool.name,
5457
]);
5558

5659
export function registerTools(context: McpContext) {
5760
const tools = [
5861
searchDocsTool,
62+
submitFeedbackTool,
5963
listOrgsTool,
6064
listProjectsTool,
6165
createProjectInOrgTool,
@@ -103,6 +107,11 @@ export function registerTools(context: McpContext) {
103107
continue;
104108
}
105109

110+
// Nothing to report to, so don't show the model a tool that can only fail.
111+
if (tool.name === submitFeedbackTool.name && !isTelemetryEnabled(context.options)) {
112+
continue;
113+
}
114+
106115
const isWrite = WRITE_TOOLS.has(tool.name);
107116

108117
context.server.registerTool(
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { z } from "zod";
2+
import { toolsMetadata } from "../config.js";
3+
import { respondWithError, toolHandler } from "../utils.js";
4+
import { captureFeedback } from "../telemetry.js";
5+
import { VERSION } from "../../version.js";
6+
7+
const MAX_MESSAGE_LENGTH = 4000;
8+
const MAX_TOOL_NAME_LENGTH = 100;
9+
// Bounded because it is sent as an analytics group key, where junk values persist.
10+
const MAX_PROJECT_REF_LENGTH = 100;
11+
12+
const SubmitFeedbackInput = {
13+
message: z
14+
.string()
15+
.min(1)
16+
.max(MAX_MESSAGE_LENGTH)
17+
.describe(
18+
"What you were trying to do, what actually happened, and what you expected instead, in your own words. Summarise the failing call and error rather than pasting raw output, and leave out secrets, credentials, environment variables and the user's own data."
19+
),
20+
toolName: z
21+
.string()
22+
.max(MAX_TOOL_NAME_LENGTH)
23+
.optional()
24+
.describe("The MCP tool the problem happened in, if it was one tool in particular."),
25+
projectRef: z
26+
.string()
27+
.max(MAX_PROJECT_REF_LENGTH)
28+
.startsWith("proj_")
29+
.optional()
30+
.describe(
31+
"The trigger.dev project ref, starts with proj_. Optional: a report without one is still useful."
32+
),
33+
};
34+
35+
export const submitFeedbackTool = {
36+
name: toolsMetadata.submit_feedback.name,
37+
title: toolsMetadata.submit_feedback.title,
38+
description: toolsMetadata.submit_feedback.description,
39+
inputSchema: SubmitFeedbackInput,
40+
handler: toolHandler(SubmitFeedbackInput, async (input, { ctx }) => {
41+
ctx.logger?.log("calling submit_feedback", {
42+
toolName: input.toolName,
43+
messageLength: input.message.length,
44+
});
45+
46+
const auth = await ctx.getAuth();
47+
48+
try {
49+
await captureFeedback({
50+
userId: auth.userId,
51+
message: input.message,
52+
toolName: input.toolName,
53+
projectRef: input.projectRef,
54+
cliVersion: VERSION,
55+
});
56+
} catch (error) {
57+
// Only this tool is affected - nothing else here reports anywhere.
58+
return respondWithError(
59+
`Your feedback was not recorded - ${
60+
error instanceof Error ? error.message : String(error)
61+
}. Tell the user the report could not be filed; every other tool still works.`
62+
);
63+
}
64+
65+
return {
66+
content: [
67+
{
68+
type: "text" as const,
69+
text: "Thanks — your feedback was sent to the Trigger.dev team. Tell the user you reported it, and carry on with the task.",
70+
},
71+
],
72+
};
73+
}),
74+
};

‎pnpm-lock.yaml‎

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)