Skip to content

Commit 3a04759

Browse files
authored
Merge pull request #1785 from myftija/mcp-server
Add support for running a local MCP server via the `dev` CLI command
2 parents 80e981c + 7d65aa9 commit 3a04759

File tree

10 files changed

+847
-8
lines changed

10 files changed

+847
-8
lines changed

.cursor/mcp.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"mcpServers": {
3+
"trigger.dev": {
4+
"url": "http://localhost:3333/sse"
5+
}
6+
}
7+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { json } from "@remix-run/server-runtime";
2+
import { z } from "zod";
3+
import { getTaskEventStoreTableForRun } from "~/v3/taskEventStore.server";
4+
import { createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server";
5+
import { eventRepository } from "~/v3/eventRepository.server";
6+
import { ApiRetrieveRunPresenter } from "~/presenters/v3/ApiRetrieveRunPresenter.server";
7+
8+
const ParamsSchema = z.object({
9+
runId: z.string(), // This is the run friendly ID
10+
});
11+
12+
// TODO: paginate the results
13+
export const loader = createLoaderApiRoute(
14+
{
15+
params: ParamsSchema,
16+
allowJWT: true,
17+
corsStrategy: "all",
18+
findResource: (params, auth) => {
19+
return ApiRetrieveRunPresenter.findRun(params.runId, auth.environment);
20+
},
21+
shouldRetryNotFound: true,
22+
authorization: {
23+
action: "read",
24+
resource: (run) => ({
25+
runs: run.friendlyId,
26+
tags: run.runTags,
27+
batch: run.batch?.friendlyId,
28+
tasks: run.taskIdentifier,
29+
}),
30+
superScopes: ["read:runs", "read:all", "admin"],
31+
},
32+
},
33+
async ({ resource: run }) => {
34+
const runEvents = await eventRepository.getRunEvents(
35+
getTaskEventStoreTableForRun(run),
36+
run.friendlyId,
37+
run.createdAt,
38+
run.completedAt ?? undefined
39+
);
40+
41+
// TODO: return only relevant fields, avoid returning the whole events
42+
return json(
43+
{
44+
events: runEvents.map((event) => {
45+
return JSON.parse(
46+
JSON.stringify(event, (_, value) =>
47+
// needed as JSON.stringify doesn't know how to handle BigInt values by default
48+
typeof value === "bigint" ? value.toString() : value
49+
)
50+
);
51+
}),
52+
},
53+
{ status: 200 }
54+
);
55+
}
56+
);

packages/cli-v3/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,37 @@ Trigger.dev is an open source platform that makes it easy to create event-driven
1919
| [list-profiles](https://trigger.dev/docs/cli-list-profiles-commands) | List all of your CLI profiles. |
2020
| [update](https://trigger.dev/docs/cli-update-commands) | Updates all `@trigger.dev/*` packages to match the CLI version. |
2121

22+
## MCP Server
23+
24+
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that allows you to provide custom tools
25+
to agentic LLM clients, like [Claude for Desktop](https://docs.anthropic.com/en/docs/claude-for-desktop/overview), [Cursor](https://www.cursor.com/), [Windsurf](https://windsurf.com/), etc...
26+
27+
The Trigger.dev CLI can expose an MCP server and enable you interact with Trigger.dev in agentic LLM workflows. For example, you can use
28+
it to trigger tasks via natural language, view task runs, view logs, debug issues with task runs, etc...
29+
30+
### Starting the Trigger.dev MCP Server
31+
32+
To start the Trigger.dev MCP server, simply pass the `--mcp` flag to the `dev` command:
33+
34+
```bash
35+
trigger dev --mcp
36+
```
37+
38+
By default it runs on port `3333`. You can change this by passing the `--mcp-port` flag:
39+
40+
```bash
41+
trigger dev --mcp --mcp-port 3334
42+
```
43+
44+
### Configuring your MCP client
45+
46+
This depends on what tool you are using. For Cursor, the configuration is in the [.cursor/mcp.json](../../.cursor/mcp.json) file
47+
and should be good to go as long as you use the default MCP server port.
48+
49+
Check out [Cursor's docs](https://docs.cursor.com/context/model-context-protocol) for further details.
50+
51+
Tip: try out [Cursor's YOLO mode](https://docs.cursor.com/context/model-context-protocol#yolo-mode) for a seamless experience :D
52+
2253
## Support
2354

2455
If you have any questions, please reach out to us on [Discord](https://trigger.dev/discord) and we'll be happy to help.

packages/cli-v3/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@types/eventsource": "^1.1.15",
5050
"@types/gradient-string": "^1.1.2",
5151
"@types/object-hash": "3.0.6",
52+
"@types/polka": "^0.5.7",
5253
"@types/react": "^18.2.48",
5354
"@types/resolve": "^1.20.6",
5455
"@types/rimraf": "^4.0.5",
@@ -75,6 +76,7 @@
7576
"dependencies": {
7677
"@clack/prompts": "^0.10.0",
7778
"@depot/cli": "0.0.1-cli.2.80.0",
79+
"@modelcontextprotocol/sdk": "^1.6.1",
7880
"@opentelemetry/api": "1.9.0",
7981
"@opentelemetry/api-logs": "0.52.1",
8082
"@opentelemetry/exporter-logs-otlp-http": "0.52.1",
@@ -114,6 +116,7 @@
114116
"p-retry": "^6.1.0",
115117
"partysocket": "^1.0.2",
116118
"pkg-types": "^1.1.3",
119+
"polka": "^0.5.2",
117120
"resolve": "^1.22.8",
118121
"semver": "^7.5.0",
119122
"signal-exit": "^4.1.0",

packages/cli-v3/src/apiClient.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
DevDequeueRequestBody,
3232
DevDequeueResponseBody,
3333
PromoteDeploymentResponseBody,
34+
ListRunResponse,
3435
} from "@trigger.dev/core/v3";
3536
import { zodfetch, zodfetchSSE, ApiError } from "@trigger.dev/core/v3/zodfetch";
3637
import { logger } from "./utilities/logger.js";
@@ -48,6 +49,7 @@ import {
4849
export class CliApiClient {
4950
constructor(
5051
public readonly apiURL: string,
52+
// TODO: consider making this required
5153
public readonly accessToken?: string
5254
) {
5355
this.apiURL = apiURL.replace(/\/$/, "");

packages/cli-v3/src/commands/dev.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const DevCommandOptions = CommonCommandOptions.extend({
2020
envFile: z.string().optional(),
2121
keepTmpFiles: z.boolean().default(false),
2222
maxConcurrentRuns: z.coerce.number().optional(),
23+
mcp: z.boolean().default(false),
24+
mcpPort: z.coerce.number().optional().default(3333),
2325
});
2426

2527
export type DevCommandOptions = z.infer<typeof DevCommandOptions>;
@@ -48,6 +50,8 @@ export function configureDevCommand(program: Command) {
4850
"--keep-tmp-files",
4951
"Keep temporary files after the dev session ends, helpful for debugging"
5052
)
53+
.option("--mcp", "Start the MCP server")
54+
.option("--mcp-port", "The port to run the MCP server on", "3333")
5155
).action(async (options) => {
5256
wrapCommandAction("dev", DevCommandOptions, options, async (opts) => {
5357
await devCommand(opts);

packages/cli-v3/src/dev/devSession.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { logger } from "../utilities/logger.js";
2323
import { clearTmpDirs, EphemeralDirectory, getTmpDir } from "../utilities/tempDirectories.js";
2424
import { startDevOutput } from "./devOutput.js";
2525
import { startWorkerRuntime } from "./devSupervisor.js";
26+
import { startMcpServer, stopMcpServer } from "./mcpServer.js";
2627

2728
export type DevSessionOptions = {
2829
name: string | undefined;
@@ -59,6 +60,17 @@ export async function startDevSession({
5960
dashboardUrl,
6061
});
6162

63+
if (rawArgs.mcp) {
64+
await startMcpServer({
65+
port: rawArgs.mcpPort,
66+
cliApiClient: client,
67+
devSession: {
68+
dashboardUrl,
69+
projectRef: rawConfig.project,
70+
},
71+
});
72+
}
73+
6274
const stopOutput = startDevOutput({
6375
name,
6476
dashboardUrl,
@@ -190,6 +202,7 @@ export async function startDevSession({
190202
stopBundling?.().catch((error) => {});
191203
runtime.shutdown().catch((error) => {});
192204
stopOutput();
205+
stopMcpServer();
193206
},
194207
};
195208
}

0 commit comments

Comments
 (0)