Skip to content

Add support for running a local MCP server via the dev CLI command #1785

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .cursor/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"mcpServers": {
"trigger.dev": {
"url": "http://localhost:3333/sse"
}
}
}
56 changes: 56 additions & 0 deletions apps/webapp/app/routes/api.v1.runs.$runId.events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { getTaskEventStoreTableForRun } from "~/v3/taskEventStore.server";
import { createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server";
import { eventRepository } from "~/v3/eventRepository.server";
import { ApiRetrieveRunPresenter } from "~/presenters/v3/ApiRetrieveRunPresenter.server";

const ParamsSchema = z.object({
runId: z.string(), // This is the run friendly ID
});

// TODO: paginate the results
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don't forget to implement pagination.

The TODO comment correctly identifies the need for pagination. Without it, large result sets could cause performance issues or timeout errors, especially if runs can generate many events.

Consider implementing standard pagination parameters (limit/offset or cursor-based) and returning metadata about total count and next page.

export const loader = createLoaderApiRoute(
{
params: ParamsSchema,
allowJWT: true,
corsStrategy: "all",
findResource: (params, auth) => {
return ApiRetrieveRunPresenter.findRun(params.runId, auth.environment);
},
shouldRetryNotFound: true,
authorization: {
action: "read",
resource: (run) => ({
runs: run.friendlyId,
tags: run.runTags,
batch: run.batch?.friendlyId,
tasks: run.taskIdentifier,
}),
superScopes: ["read:runs", "read:all", "admin"],
},
},
async ({ resource: run }) => {
const runEvents = await eventRepository.getRunEvents(
getTaskEventStoreTableForRun(run),
run.friendlyId,
run.createdAt,
run.completedAt ?? undefined
);

// TODO: return only relevant fields, avoid returning the whole events
return json(
{
events: runEvents.map((event) => {
return JSON.parse(
JSON.stringify(event, (_, value) =>
// needed as JSON.stringify doesn't know how to handle BigInt values by default
typeof value === "bigint" ? value.toString() : value
)
);
}),
},
{ status: 200 }
);
}
);
31 changes: 31 additions & 0 deletions packages/cli-v3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,37 @@ Trigger.dev is an open source platform that makes it easy to create event-driven
| [list-profiles](https://trigger.dev/docs/cli-list-profiles-commands) | List all of your CLI profiles. |
| [update](https://trigger.dev/docs/cli-update-commands) | Updates all `@trigger.dev/*` packages to match the CLI version. |

## MCP Server

The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that allows you to provide custom tools
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...

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
it to trigger tasks via natural language, view task runs, view logs, debug issues with task runs, etc...

### Starting the Trigger.dev MCP Server

To start the Trigger.dev MCP server, simply pass the `--mcp` flag to the `dev` command:

```bash
trigger dev --mcp
```

By default it runs on port `3333`. You can change this by passing the `--mcp-port` flag:

```bash
trigger dev --mcp --mcp-port 3334
```

### Configuring your MCP client

This depends on what tool you are using. For Cursor, the configuration is in the [.cursor/mcp.json](../../.cursor/mcp.json) file
and should be good to go as long as you use the default MCP server port.

Check out [Cursor's docs](https://docs.cursor.com/context/model-context-protocol) for further details.

Tip: try out [Cursor's YOLO mode](https://docs.cursor.com/context/model-context-protocol#yolo-mode) for a seamless experience :D

## Support

If you have any questions, please reach out to us on [Discord](https://trigger.dev/discord) and we'll be happy to help.
3 changes: 3 additions & 0 deletions packages/cli-v3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@types/eventsource": "^1.1.15",
"@types/gradient-string": "^1.1.2",
"@types/object-hash": "3.0.6",
"@types/polka": "^0.5.7",
"@types/react": "^18.2.48",
"@types/resolve": "^1.20.6",
"@types/rimraf": "^4.0.5",
Expand All @@ -75,6 +76,7 @@
"dependencies": {
"@clack/prompts": "^0.10.0",
"@depot/cli": "0.0.1-cli.2.80.0",
"@modelcontextprotocol/sdk": "^1.6.1",
"@opentelemetry/api": "1.9.0",
"@opentelemetry/api-logs": "0.52.1",
"@opentelemetry/exporter-logs-otlp-http": "0.52.1",
Expand Down Expand Up @@ -114,6 +116,7 @@
"p-retry": "^6.1.0",
"partysocket": "^1.0.2",
"pkg-types": "^1.1.3",
"polka": "^0.5.2",
"resolve": "^1.22.8",
"semver": "^7.5.0",
"signal-exit": "^4.1.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli-v3/src/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
DevDequeueRequestBody,
DevDequeueResponseBody,
PromoteDeploymentResponseBody,
ListRunResponse,
} from "@trigger.dev/core/v3";
import { zodfetch, zodfetchSSE, ApiError } from "@trigger.dev/core/v3/zodfetch";
import { logger } from "./utilities/logger.js";
Expand All @@ -48,6 +49,7 @@ import {
export class CliApiClient {
constructor(
public readonly apiURL: string,
// TODO: consider making this required
public readonly accessToken?: string
) {
this.apiURL = apiURL.replace(/\/$/, "");
Expand Down
4 changes: 4 additions & 0 deletions packages/cli-v3/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const DevCommandOptions = CommonCommandOptions.extend({
envFile: z.string().optional(),
keepTmpFiles: z.boolean().default(false),
maxConcurrentRuns: z.coerce.number().optional(),
mcp: z.boolean().default(false),
mcpPort: z.coerce.number().optional().default(3333),
});

export type DevCommandOptions = z.infer<typeof DevCommandOptions>;
Expand Down Expand Up @@ -48,6 +50,8 @@ export function configureDevCommand(program: Command) {
"--keep-tmp-files",
"Keep temporary files after the dev session ends, helpful for debugging"
)
.option("--mcp", "Start the MCP server")
.option("--mcp-port", "The port to run the MCP server on", "3333")
).action(async (options) => {
wrapCommandAction("dev", DevCommandOptions, options, async (opts) => {
await devCommand(opts);
Expand Down
13 changes: 13 additions & 0 deletions packages/cli-v3/src/dev/devSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { logger } from "../utilities/logger.js";
import { clearTmpDirs, EphemeralDirectory, getTmpDir } from "../utilities/tempDirectories.js";
import { startDevOutput } from "./devOutput.js";
import { startWorkerRuntime } from "./devSupervisor.js";
import { startMcpServer, stopMcpServer } from "./mcpServer.js";

export type DevSessionOptions = {
name: string | undefined;
Expand Down Expand Up @@ -59,6 +60,17 @@ export async function startDevSession({
dashboardUrl,
});

if (rawArgs.mcp) {
await startMcpServer({
port: rawArgs.mcpPort,
cliApiClient: client,
devSession: {
dashboardUrl,
projectRef: rawConfig.project,
},
});
}

const stopOutput = startDevOutput({
name,
dashboardUrl,
Expand Down Expand Up @@ -190,6 +202,7 @@ export async function startDevSession({
stopBundling?.().catch((error) => {});
runtime.shutdown().catch((error) => {});
stopOutput();
stopMcpServer();
},
};
}
Loading
Loading