From 5c0158e98b1b8a79a29c02e2576d7b848ca07db9 Mon Sep 17 00:00:00 2001 From: saadi Date: Tue, 11 Mar 2025 12:11:31 +0100 Subject: [PATCH 01/12] Expose a basic version of the MCP server as an option for the `dev` CLI command This just adds the main components for the MCP server. It hooks on the existing `dev` command and can be enabled by passing the `--mcp` flag. Currently only the `trigger-task` tool is exposed, which enables users trigger tasks via MCP and see the resulting run. Still WIP :) --- packages/cli-v3/package.json | 3 + packages/cli-v3/src/apiClient.ts | 2 + packages/cli-v3/src/commands/dev.ts | 3 + packages/cli-v3/src/dev/devSession.ts | 12 + packages/cli-v3/src/dev/mcpServer.ts | 82 +++++ pnpm-lock.yaml | 459 +++++++++++++++++++++++++- 6 files changed, 554 insertions(+), 7 deletions(-) create mode 100644 packages/cli-v3/src/dev/mcpServer.ts diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index 1992498e5b..d87c1520ff 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -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", @@ -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", @@ -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", diff --git a/packages/cli-v3/src/apiClient.ts b/packages/cli-v3/src/apiClient.ts index 7defa22cc1..07d46621e9 100644 --- a/packages/cli-v3/src/apiClient.ts +++ b/packages/cli-v3/src/apiClient.ts @@ -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"; @@ -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(/\/$/, ""); diff --git a/packages/cli-v3/src/commands/dev.ts b/packages/cli-v3/src/commands/dev.ts index 479a472171..9cdc2f8c11 100644 --- a/packages/cli-v3/src/commands/dev.ts +++ b/packages/cli-v3/src/commands/dev.ts @@ -20,6 +20,7 @@ const DevCommandOptions = CommonCommandOptions.extend({ envFile: z.string().optional(), keepTmpFiles: z.boolean().default(false), maxConcurrentRuns: z.coerce.number().optional(), + mcp: z.boolean().default(false), }); export type DevCommandOptions = z.infer; @@ -48,6 +49,8 @@ export function configureDevCommand(program: Command) { "--keep-tmp-files", "Keep temporary files after the dev session ends, helpful for debugging" ) + // TODO: add a more detailed description, maybe a pointer to the docs on how to use MCP + .option("--mcp", "Run an MCP server") ).action(async (options) => { wrapCommandAction("dev", DevCommandOptions, options, async (opts) => { await devCommand(opts); diff --git a/packages/cli-v3/src/dev/devSession.ts b/packages/cli-v3/src/dev/devSession.ts index a9cc1fe6f3..597ccdd118 100644 --- a/packages/cli-v3/src/dev/devSession.ts +++ b/packages/cli-v3/src/dev/devSession.ts @@ -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 } from "./mcpServer.js"; export type DevSessionOptions = { name: string | undefined; @@ -59,6 +60,16 @@ export async function startDevSession({ dashboardUrl, }); + if (rawArgs.mcp) { + await startMcpServer({ + cliApiClient: client, + devSession: { + dashboardUrl, + projectRef: rawConfig.project, + }, + }); + } + const stopOutput = startDevOutput({ name, dashboardUrl, @@ -190,6 +201,7 @@ export async function startDevSession({ stopBundling?.().catch((error) => {}); runtime.shutdown().catch((error) => {}); stopOutput(); + // TODO: stop MCP server }, }; } diff --git a/packages/cli-v3/src/dev/mcpServer.ts b/packages/cli-v3/src/dev/mcpServer.ts new file mode 100644 index 0000000000..55a4821810 --- /dev/null +++ b/packages/cli-v3/src/dev/mcpServer.ts @@ -0,0 +1,82 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import { z } from "zod"; +import { logger } from "../utilities/logger.js"; +import { CliApiClient } from "../apiClient.js"; +import { ApiClient } from "@trigger.dev/core/v3"; +import polka from "polka"; + +let projectRef: string; +let dashboardUrl: string; +// there is some overlap between `ApiClient` and `CliApiClient` which is not ideal +// we can address in this in the future, but for now we need keep using both +// as `ApiClient` exposes most of the methods needed for the MCP tools +let sdkApiClient: ApiClient; + +let mcpTransport: SSEServerTransport | null = null; + +const server = new McpServer({ + name: "trigger.dev", + version: "1.0.0", +}); + +server.tool( + "trigger-task", + "Trigger a task", + { + id: z.string().describe("The id of the task to trigger"), + }, + async ({ id }) => { + const result = await sdkApiClient.triggerTask(id, { + // TODO: enable the user to pass in a custom payload + payload: {}, + }); + + const taskRunUrl = `${dashboardUrl}/projects/v3/${projectRef}/runs/${result.id}`; + + return { + content: [ + { + type: "text", + text: JSON.stringify({ ...result, taskRunUrl }, null, 2), + }, + ], + }; + } +); + +const app = polka(); +app.get("/sse", (_req, res) => { + mcpTransport = new SSEServerTransport("/messages", res); + server.connect(mcpTransport); +}); +app.post("/messages", (req, res) => { + if (mcpTransport) { + mcpTransport.handlePostMessage(req, res); + } +}); + +export const startMcpServer = async (options: { + cliApiClient: CliApiClient; + devSession: { + dashboardUrl: string; + projectRef: string; + }; +}) => { + const { apiURL, accessToken } = options.cliApiClient; + + if (!accessToken) { + logger.error("No access token found in the API client, failed to start the MCP server"); + return; + } + + sdkApiClient = new ApiClient(apiURL, accessToken); + projectRef = options.devSession.projectRef; + dashboardUrl = options.devSession.dashboardUrl; + + // TODO: make the port configurable + const port = 3333; + app.listen(port, () => { + logger.info(`Trigger.dev MCP Server is now running on port ${port} ✨`); + }); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c79c6bf79..52ab355a58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1160,6 +1160,9 @@ importers: '@depot/cli': specifier: 0.0.1-cli.2.80.0 version: 0.0.1-cli.2.80.0 + '@modelcontextprotocol/sdk': + specifier: ^1.6.1 + version: 1.6.1 '@opentelemetry/api': specifier: 1.9.0 version: 1.9.0 @@ -1277,6 +1280,9 @@ importers: pkg-types: specifier: ^1.1.3 version: 1.1.3 + polka: + specifier: ^0.5.2 + version: 0.5.2 resolve: specifier: ^1.22.8 version: 1.22.8 @@ -1332,6 +1338,9 @@ importers: '@types/object-hash': specifier: 3.0.6 version: 3.0.6 + '@types/polka': + specifier: ^0.5.7 + version: 0.5.7 '@types/react': specifier: ^18.2.48 version: 18.2.48 @@ -2516,6 +2525,11 @@ packages: resolution: {integrity: sha512-TpHY532LKQwwYHui5NN/eO/6eSiSMvf652YNt1BsV7fya7RzXL27IiU9x4bm7jTFZxLQGYDQTB7nw41TqeuF4g==} dev: false + /@arr/every@1.0.1: + resolution: {integrity: sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==} + engines: {node: '>=4'} + dev: false + /@aws-crypto/crc32@3.0.0: resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==} dependencies: @@ -3552,7 +3566,7 @@ packages: '@babel/types': 7.26.8 '@types/gensync': 1.0.4 convert-source-map: 2.0.0 - debug: 4.3.7 + debug: 4.4.0 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -5234,7 +5248,7 @@ packages: '@babel/parser': 7.26.8 '@babel/template': 7.26.8 '@babel/types': 7.26.8 - debug: 4.3.7 + debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -7442,7 +7456,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.7 + debug: 4.4.0 espree: 9.6.0 globals: 13.19.0 ignore: 5.2.4 @@ -7712,7 +7726,7 @@ packages: deprecated: Use @eslint/config-array instead dependencies: '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.7 + debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -8200,6 +8214,23 @@ packages: - supports-color dev: true + /@modelcontextprotocol/sdk@1.6.1: + resolution: {integrity: sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA==} + engines: {node: '>=18'} + dependencies: + content-type: 1.0.5 + cors: 2.8.5 + eventsource: 3.0.5 + express: 5.0.1 + express-rate-limit: 7.5.0(express@5.0.1) + pkce-challenge: 4.1.0 + raw-body: 3.0.0 + zod: 3.23.8 + zod-to-json-schema: 3.24.3(zod@3.23.8) + transitivePeerDependencies: + - supports-color + dev: false + /@mrleebo/prisma-ast@0.7.0: resolution: {integrity: sha512-GTPkYf1meO2UXXIrz/SIDFWz+P4kXo2PTt36LYh/oNxV1PieYi7ZgenQk4IV0ut71Je3Z8ZoNZ8Tr7v2c1X1pg==} engines: {node: '>=16'} @@ -9596,6 +9627,10 @@ packages: optionalDependencies: fsevents: 2.3.2 + /@polka/url@0.5.0: + resolution: {integrity: sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==} + dev: false + /@polka/url@1.0.0-next.28: resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} dev: true @@ -17020,7 +17055,7 @@ packages: /@types/express-serve-static-core@4.17.32: resolution: {integrity: sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA==} dependencies: - '@types/node': 18.19.20 + '@types/node': 20.14.14 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 dev: true @@ -17253,6 +17288,15 @@ packages: pg-protocol: 1.6.1 pg-types: 2.2.0 + /@types/polka@0.5.7: + resolution: {integrity: sha512-TH8CDXM8zoskPCNmWabtK7ziGv9Q21s4hMZLVYK5HFEfqmGXBqq/Wgi7jNELWXftZK/1J/9CezYa06x1RKeQ+g==} + dependencies: + '@types/express': 4.17.15 + '@types/express-serve-static-core': 4.17.32 + '@types/node': 20.14.14 + '@types/trouter': 3.1.4 + dev: true + /@types/prismjs@1.26.0: resolution: {integrity: sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==} @@ -17439,6 +17483,10 @@ packages: resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} dev: false + /@types/trouter@3.1.4: + resolution: {integrity: sha512-4YIL/2AvvZqKBWenjvEpxpblT2KGO6793ipr5QS7/6DpQ3O3SwZGgNGWezxf3pzeYZc24a2pJIrR/+Jxh/wYNQ==} + dev: true + /@types/unist@2.0.6: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: true @@ -18209,6 +18257,14 @@ packages: mime-types: 2.1.35 negotiator: 0.6.3 + /accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 3.0.0 + negotiator: 1.0.0 + dev: false + /acorn-import-assertions@1.9.0(acorn@8.12.1): resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} peerDependencies: @@ -19216,6 +19272,23 @@ packages: transitivePeerDependencies: - supports-color + /body-parser@2.1.0: + resolution: {integrity: sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==} + engines: {node: '>=18'} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.0 + http-errors: 2.0.0 + iconv-lite: 0.5.2 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.0 + type-is: 2.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} dev: false @@ -19430,6 +19503,14 @@ packages: responselike: 1.0.2 dev: true + /call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + dev: false + /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: @@ -19447,6 +19528,14 @@ packages: get-intrinsic: 1.2.4 set-function-length: 1.2.2 + /call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + dev: false + /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -20017,6 +20106,13 @@ packages: dependencies: safe-buffer: 5.2.1 + /content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + /content-type@1.0.4: resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} engines: {node: '>= 0.6'} @@ -20038,6 +20134,11 @@ packages: resolution: {integrity: sha512-R0BOPfLGTitaKhgKROKZQN6iyq2iDQcH1DOF8nJoaWapguX5bC2w+Q/I9NmmM5lfcvEarnLZr+cCvmEYYSXvYA==} engines: {node: '>=6.6.0'} + /cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + dev: false + /cookie@0.4.2: resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} engines: {node: '>= 0.6'} @@ -20051,6 +20152,11 @@ packages: engines: {node: '>= 0.6'} dev: false + /cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + dev: false + /cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} dev: true @@ -20549,7 +20655,6 @@ packages: optional: true dependencies: ms: 2.1.2 - dev: true /debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} @@ -20562,6 +20667,17 @@ packages: dependencies: ms: 2.1.3 + /debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -20942,6 +21058,15 @@ packages: detect-libc: 1.0.3 dev: true + /dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + dev: false + /duplexer3@0.1.5: resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} dev: true @@ -21027,6 +21152,11 @@ packages: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + /encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + dev: false + /encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} requiresBuild: true @@ -21213,6 +21343,11 @@ packages: dependencies: get-intrinsic: 1.2.4 + /es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + dev: false + /es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -21227,6 +21362,13 @@ packages: es-errors: 1.3.0 dev: true + /es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + dev: false + /es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} engines: {node: '>= 0.4'} @@ -22172,7 +22314,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.7 + debug: 4.4.0 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.0 @@ -22406,6 +22548,15 @@ packages: resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} dev: false + /express-rate-limit@7.5.0(express@5.0.1): + resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==} + engines: {node: '>= 16'} + peerDependencies: + express: ^4.11 || 5 || ^5.0.0-beta.1 + dependencies: + express: 5.0.1 + dev: false + /express@4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} @@ -22444,6 +22595,46 @@ packages: transitivePeerDependencies: - supports-color + /express@5.0.1: + resolution: {integrity: sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==} + engines: {node: '>= 18'} + dependencies: + accepts: 2.0.0 + body-parser: 2.1.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.2.2 + debug: 4.3.6 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + methods: 1.1.2 + mime-types: 3.0.0 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + router: 2.1.0 + safe-buffer: 5.2.1 + send: 1.1.0 + serve-static: 2.1.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 2.0.0 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false + /extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -22654,6 +22845,20 @@ packages: transitivePeerDependencies: - supports-color + /finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + dependencies: + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + /find-cache-dir@3.3.2: resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} engines: {node: '>=8'} @@ -22867,6 +23072,11 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + /fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + dev: false + /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -23004,6 +23214,22 @@ packages: has-symbols: 1.0.3 hasown: 2.0.2 + /get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + dev: false + /get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} @@ -23013,6 +23239,14 @@ packages: resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} engines: {node: '>=8'} + /get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + dev: false + /get-source@2.0.12: resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} dependencies: @@ -23254,6 +23488,11 @@ packages: dependencies: get-intrinsic: 1.2.4 + /gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + dev: false + /got@9.6.0: resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==} engines: {node: '>=8.6'} @@ -23404,6 +23643,11 @@ packages: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + /has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + dev: false + /has-tostringtag@1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} @@ -23636,6 +23880,13 @@ packages: dependencies: safer-buffer: 2.1.2 + /iconv-lite@0.5.2: + resolution: {integrity: sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + /iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -24075,6 +24326,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + dev: false + /is-reference@3.0.1: resolution: {integrity: sha512-baJJdQLiYaJdvFbJqXrcGv3WU3QCzBlUcI5QhbesIm6/xPsvmO+2CDoi/GMOFBQEQm+PXkwOPrp9KK5ozZsp2w==} dependencies: @@ -24932,6 +25187,18 @@ packages: remove-accents: 0.5.0 dev: false + /matchit@1.1.0: + resolution: {integrity: sha512-+nGYoOlfHmxe5BW5tE0EMJppXEwdSf8uBA1GTZC7Q77kbT35+VKLYJMzVNWCHSsga1ps1tPYFtFyvxvKzWVmMA==} + engines: {node: '>=6'} + dependencies: + '@arr/every': 1.0.1 + dev: false + + /math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + dev: false + /md-to-react-email@5.0.2(react@18.3.1): resolution: {integrity: sha512-x6kkpdzIzUhecda/yahltfEl53mH26QdWu4abUF9+S0Jgam8P//Ciro8cdhyMHnT5MQUJYrIbO6ORM2UxPiNNA==} peerDependencies: @@ -25092,6 +25359,11 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + /media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + dev: false + /memorystream@0.3.1: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} @@ -25122,6 +25394,11 @@ packages: /merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + /merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + dev: false + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -25411,12 +25688,24 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + /mime-db@1.53.0: + resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==} + engines: {node: '>= 0.6'} + dev: false + /mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 + /mime-types@3.0.0: + resolution: {integrity: sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.53.0 + dev: false + /mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -25840,6 +26129,11 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + /negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + dev: false + /neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -26272,6 +26566,11 @@ packages: engines: {node: '>= 0.4'} dev: true + /object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + dev: false + /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -26918,6 +27217,11 @@ packages: /path-to-regexp@6.2.1: resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + /path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} + dev: false + /path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} @@ -27113,6 +27417,11 @@ packages: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} + /pkce-challenge@4.1.0: + resolution: {integrity: sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==} + engines: {node: '>=16.20.0'} + dev: false + /pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -27143,6 +27452,13 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true + /polka@0.5.2: + resolution: {integrity: sha512-FVg3vDmCqP80tOrs+OeNlgXYmFppTXdjD5E7I4ET1NjvtNmQrb1/mJibybKkb/d4NA7YWAr1ojxuhpL3FHqdlw==} + dependencies: + '@polka/url': 0.5.0 + trouter: 2.0.1 + dev: false + /possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -27918,6 +28234,20 @@ packages: dependencies: side-channel: 1.0.4 + /qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.1.0 + dev: false + + /qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.1.0 + dev: false + /qs@6.5.3: resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} engines: {node: '>=0.6'} @@ -27962,6 +28292,16 @@ packages: iconv-lite: 0.4.24 unpipe: 1.0.0 + /raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + dev: false + /rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} dependencies: @@ -29209,6 +29549,15 @@ packages: fsevents: 2.3.3 dev: true + /router@2.1.0: + resolution: {integrity: sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==} + engines: {node: '>= 18'} + dependencies: + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.2.0 + dev: false + /rtl-css-js@1.16.1: resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} dependencies: @@ -29391,6 +29740,26 @@ packages: transitivePeerDependencies: - supports-color + /send@1.1.0: + resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} + engines: {node: '>= 18'} + dependencies: + debug: 4.3.7 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime-types: 2.1.35 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + /serialize-javascript@6.0.1: resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==} dependencies: @@ -29407,6 +29776,18 @@ packages: transitivePeerDependencies: - supports-color + /serve-static@2.1.0: + resolution: {integrity: sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==} + engines: {node: '>= 18'} + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.1.0 + transitivePeerDependencies: + - supports-color + dev: false + /server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} dev: false @@ -29492,6 +29873,35 @@ packages: /shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + /side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + dev: false + + /side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + dev: false + + /side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + dev: false + /side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: @@ -29499,6 +29909,17 @@ packages: get-intrinsic: 1.2.4 object-inspect: 1.12.2 + /side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + dev: false + /siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} dev: true @@ -30941,6 +31362,13 @@ packages: resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} dev: true + /trouter@2.0.1: + resolution: {integrity: sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==} + engines: {node: '>=6'} + dependencies: + matchit: 1.1.0 + dev: false + /ts-easing@0.2.0: resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==} dev: false @@ -31335,6 +31763,15 @@ packages: media-typer: 0.3.0 mime-types: 2.1.35 + /type-is@2.0.0: + resolution: {integrity: sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==} + engines: {node: '>= 0.6'} + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.0 + dev: false + /typed-array-buffer@1.0.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} @@ -33213,6 +33650,14 @@ packages: dependencies: zod: 3.23.8 + /zod-to-json-schema@3.24.3(zod@3.23.8): + resolution: {integrity: sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==} + peerDependencies: + zod: ^3.24.1 + dependencies: + zod: 3.23.8 + dev: false + /zod-validation-error@1.5.0(zod@3.23.8): resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==} engines: {node: '>=16.0.0'} From e00afc133282f7b71d27ceecb5fc05f6e232b720 Mon Sep 17 00:00:00 2001 From: saadi Date: Tue, 11 Mar 2025 12:58:11 +0100 Subject: [PATCH 02/12] Use the v2 run engine by default in the SDK API client V2 is the way to go for the future. --- packages/core/src/v3/apiClient/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/v3/apiClient/index.ts b/packages/core/src/v3/apiClient/index.ts index feab729141..873a0b3122 100644 --- a/packages/core/src/v3/apiClient/index.ts +++ b/packages/core/src/v3/apiClient/index.ts @@ -801,7 +801,7 @@ export class ApiClient { "Content-Type": "application/json", Authorization: `Bearer ${this.accessToken}`, "trigger-version": VERSION, - "x-trigger-engine-version": taskContext.worker?.engine ?? "V1", + "x-trigger-engine-version": taskContext.worker?.engine ?? "V2", ...Object.entries(additionalHeaders ?? {}).reduce( (acc, [key, value]) => { if (value !== undefined) { From ce3e49b4e3273b970987e08f94858949edf15f54 Mon Sep 17 00:00:00 2001 From: saadi Date: Tue, 11 Mar 2025 14:05:07 +0100 Subject: [PATCH 03/12] Enable passing custom payload in the trigger-task MCP tool --- packages/cli-v3/src/dev/mcpServer.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/cli-v3/src/dev/mcpServer.ts b/packages/cli-v3/src/dev/mcpServer.ts index 55a4821810..a13180bdab 100644 --- a/packages/cli-v3/src/dev/mcpServer.ts +++ b/packages/cli-v3/src/dev/mcpServer.ts @@ -24,12 +24,25 @@ server.tool( "trigger-task", "Trigger a task", { - id: z.string().describe("The id of the task to trigger"), + id: z.string().describe("The ID of the task to trigger"), + payload: z + .string() + .transform((val, ctx) => { + try { + return JSON.parse(val); + } catch { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "The payload must be a valid JSON string", + }); + return z.NEVER; + } + }) + .describe("The payload to pass to the task run, must be a valid JSON"), }, - async ({ id }) => { + async ({ id, payload }) => { const result = await sdkApiClient.triggerTask(id, { - // TODO: enable the user to pass in a custom payload - payload: {}, + payload, }); const taskRunUrl = `${dashboardUrl}/projects/v3/${projectRef}/runs/${result.id}`; From 1d7224d2ee10434896a496c653e06225476582b7 Mon Sep 17 00:00:00 2001 From: saadi Date: Tue, 11 Mar 2025 14:22:42 +0100 Subject: [PATCH 04/12] Add MCP tool to list runs --- packages/cli-v3/src/dev/mcpServer.ts | 57 +++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/packages/cli-v3/src/dev/mcpServer.ts b/packages/cli-v3/src/dev/mcpServer.ts index a13180bdab..1591f27bd7 100644 --- a/packages/cli-v3/src/dev/mcpServer.ts +++ b/packages/cli-v3/src/dev/mcpServer.ts @@ -3,7 +3,7 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { z } from "zod"; import { logger } from "../utilities/logger.js"; import { CliApiClient } from "../apiClient.js"; -import { ApiClient } from "@trigger.dev/core/v3"; +import { ApiClient, RunStatus } from "@trigger.dev/core/v3"; import polka from "polka"; let projectRef: string; @@ -39,6 +39,7 @@ server.tool( } }) .describe("The payload to pass to the task run, must be a valid JSON"), + // TODO: expose more parameteres from the trigger options }, async ({ id, payload }) => { const result = await sdkApiClient.triggerTask(id, { @@ -58,6 +59,60 @@ server.tool( } ); +server.tool( + "list-runs", + "List task runs", + { + filters: z + .object({ + status: RunStatus.optional().describe( + "The status of the run. Can be WAITING_FOR_DEPLOY, QUEUED, EXECUTING, REATTEMPTING, or FROZEN" + ), + taskIdentifier: z + .union([z.array(z.string()), z.string()]) + .optional() + .describe("The identifier of the task that was run"), + version: z + .union([z.array(z.string()), z.string()]) + .optional() + .describe("The version of the worker that executed the run"), + from: z + .union([z.date(), z.number()]) + .optional() + .describe("Start date/time for filtering runs"), + to: z.union([z.date(), z.number()]).optional().describe("End date/time for filtering runs"), + period: z.string().optional().describe("Time period for filtering runs"), + bulkAction: z + .string() + .optional() + .describe("The bulk action ID to filter the runs by (e.g., bulk_1234)"), + tag: z + .union([z.array(z.string()), z.string()]) + .optional() + .describe("The tags that are attached to the run"), + schedule: z + .string() + .optional() + .describe("The schedule ID to filter the runs by (e.g., schedule_1234)"), + isTest: z.boolean().optional().describe("Whether the run is a test run or not"), + batch: z.string().optional().describe("The batch identifier to filter runs by"), + }) + .describe("Parameters for listing task runs"), + }, + async ({ filters }) => { + const { data, pagination } = await sdkApiClient.listRuns(filters); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ data, pagination }, null, 2), + }, + ], + }; + } +); + const app = polka(); app.get("/sse", (_req, res) => { mcpTransport = new SSEServerTransport("/messages", res); From af3af9ca1721d1ae030fa6233301a16c6050d695 Mon Sep 17 00:00:00 2001 From: saadi Date: Tue, 11 Mar 2025 14:33:51 +0100 Subject: [PATCH 05/12] Add MCP tool to get a single run This can be used in combination with the trigger-task tool from LLMs to show details about the run after triggering the task. --- packages/cli-v3/src/dev/mcpServer.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/cli-v3/src/dev/mcpServer.ts b/packages/cli-v3/src/dev/mcpServer.ts index 1591f27bd7..ebca5290e7 100644 --- a/packages/cli-v3/src/dev/mcpServer.ts +++ b/packages/cli-v3/src/dev/mcpServer.ts @@ -1,6 +1,6 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { z } from "zod"; +import { optional, union, z } from "zod"; import { logger } from "../utilities/logger.js"; import { CliApiClient } from "../apiClient.js"; import { ApiClient, RunStatus } from "@trigger.dev/core/v3"; @@ -61,7 +61,7 @@ server.tool( server.tool( "list-runs", - "List task runs", + "List task runs. This returns a paginated list which shows the details of the runs, e.g., status, attempts, cost, etc.", { filters: z .object({ @@ -113,6 +113,26 @@ server.tool( } ); +server.tool( + "get-run", + "Retrieve the details of a task run, e.g., status, attempts, cost, etc.", + { + runId: z.string().describe("The ID of the task run to get"), + }, + async ({ runId }) => { + const result = await sdkApiClient.retrieveRun(runId); + + return { + content: [ + { + type: "text", + text: JSON.stringify(result, null, 2), + }, + ], + }; + } +); + const app = polka(); app.get("/sse", (_req, res) => { mcpTransport = new SSEServerTransport("/messages", res); From 2e7b1a7b1cb167fdec5a4f08a7eac23c1e99f3e5 Mon Sep 17 00:00:00 2001 From: saadi Date: Tue, 11 Mar 2025 14:47:11 +0100 Subject: [PATCH 06/12] Make the MCP server port configurable via a flag in the `dev` command --- packages/cli-v3/src/commands/dev.ts | 4 +++- packages/cli-v3/src/dev/devSession.ts | 1 + packages/cli-v3/src/dev/mcpServer.ts | 7 +++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/cli-v3/src/commands/dev.ts b/packages/cli-v3/src/commands/dev.ts index 9cdc2f8c11..b286341d0b 100644 --- a/packages/cli-v3/src/commands/dev.ts +++ b/packages/cli-v3/src/commands/dev.ts @@ -21,6 +21,7 @@ const DevCommandOptions = CommonCommandOptions.extend({ 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; @@ -50,7 +51,8 @@ export function configureDevCommand(program: Command) { "Keep temporary files after the dev session ends, helpful for debugging" ) // TODO: add a more detailed description, maybe a pointer to the docs on how to use MCP - .option("--mcp", "Run an MCP server") + .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); diff --git a/packages/cli-v3/src/dev/devSession.ts b/packages/cli-v3/src/dev/devSession.ts index 597ccdd118..1f577dbec9 100644 --- a/packages/cli-v3/src/dev/devSession.ts +++ b/packages/cli-v3/src/dev/devSession.ts @@ -62,6 +62,7 @@ export async function startDevSession({ if (rawArgs.mcp) { await startMcpServer({ + port: rawArgs.mcpPort, cliApiClient: client, devSession: { dashboardUrl, diff --git a/packages/cli-v3/src/dev/mcpServer.ts b/packages/cli-v3/src/dev/mcpServer.ts index ebca5290e7..3546b9a479 100644 --- a/packages/cli-v3/src/dev/mcpServer.ts +++ b/packages/cli-v3/src/dev/mcpServer.ts @@ -145,6 +145,7 @@ app.post("/messages", (req, res) => { }); export const startMcpServer = async (options: { + port: number; cliApiClient: CliApiClient; devSession: { dashboardUrl: string; @@ -162,9 +163,7 @@ export const startMcpServer = async (options: { projectRef = options.devSession.projectRef; dashboardUrl = options.devSession.dashboardUrl; - // TODO: make the port configurable - const port = 3333; - app.listen(port, () => { - logger.info(`Trigger.dev MCP Server is now running on port ${port} ✨`); + app.listen(options.port, () => { + logger.info(`Trigger.dev MCP Server is now running on port ${options.port} ✨`); }); }; From 9cd6d40accc65324631e8b3c8bedfb1ad4f0df5a Mon Sep 17 00:00:00 2001 From: saadi Date: Tue, 11 Mar 2025 14:53:55 +0100 Subject: [PATCH 07/12] Shut down the MCP server when the dev session stops --- packages/cli-v3/src/dev/devSession.ts | 4 ++-- packages/cli-v3/src/dev/mcpServer.ts | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/cli-v3/src/dev/devSession.ts b/packages/cli-v3/src/dev/devSession.ts index 1f577dbec9..4db9a2d967 100644 --- a/packages/cli-v3/src/dev/devSession.ts +++ b/packages/cli-v3/src/dev/devSession.ts @@ -23,7 +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 } from "./mcpServer.js"; +import { startMcpServer, stopMcpServer } from "./mcpServer.js"; export type DevSessionOptions = { name: string | undefined; @@ -202,7 +202,7 @@ export async function startDevSession({ stopBundling?.().catch((error) => {}); runtime.shutdown().catch((error) => {}); stopOutput(); - // TODO: stop MCP server + stopMcpServer(); }, }; } diff --git a/packages/cli-v3/src/dev/mcpServer.ts b/packages/cli-v3/src/dev/mcpServer.ts index 3546b9a479..cb22aa8139 100644 --- a/packages/cli-v3/src/dev/mcpServer.ts +++ b/packages/cli-v3/src/dev/mcpServer.ts @@ -167,3 +167,9 @@ export const startMcpServer = async (options: { logger.info(`Trigger.dev MCP Server is now running on port ${options.port} ✨`); }); }; + +export const stopMcpServer = () => { + app.server?.close(() => { + logger.info(`Trigger.dev MCP Server is now stopped`); + }); +}; From b6b25b05f529d3c12fa1967beb6354b2935215d7 Mon Sep 17 00:00:00 2001 From: saadi Date: Tue, 11 Mar 2025 15:11:54 +0100 Subject: [PATCH 08/12] Add MCP tool to cancel task runs --- packages/cli-v3/src/dev/mcpServer.ts | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/cli-v3/src/dev/mcpServer.ts b/packages/cli-v3/src/dev/mcpServer.ts index cb22aa8139..1ddfa9ca3c 100644 --- a/packages/cli-v3/src/dev/mcpServer.ts +++ b/packages/cli-v3/src/dev/mcpServer.ts @@ -133,6 +133,55 @@ server.tool( } ); +server.tool( + "cancel-run", + "Cancel an in-progress run. Runs that have already completed cannot be cancelled.", + { + runId: z.string().describe("The ID of the task run to cancel"), + }, + async ({ runId }) => { + const run = await sdkApiClient.retrieveRun(runId); + + if (run?.status === "COMPLETED") { + return { + content: [ + { + type: "text", + text: JSON.stringify( + { message: "This run is already completed, no action taken.", run }, + null, + 2 + ), + }, + ], + }; + } + + await sdkApiClient.cancelRun(runId); + // we could also skip fetching the run again, but it provides more context to the LLM + // and one extra API call is no big deal + const updatedRun = await sdkApiClient.retrieveRun(runId); + + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + message: "Task run was cancelled", + previousStatus: run.status, + currentStatus: updatedRun.status, + updatedTaskRun: updatedRun, + }, + null, + 2 + ), + }, + ], + }; + } +); + const app = polka(); app.get("/sse", (_req, res) => { mcpTransport = new SSEServerTransport("/messages", res); From ed1f4cea1219c28f23e9a2615fa66235710b6469 Mon Sep 17 00:00:00 2001 From: saadi Date: Tue, 11 Mar 2025 16:20:12 +0100 Subject: [PATCH 09/12] Expose a new API endpoint to list events for a given task run --- .../app/routes/api.v1.runs.$runId.events.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 apps/webapp/app/routes/api.v1.runs.$runId.events.ts diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.events.ts b/apps/webapp/app/routes/api.v1.runs.$runId.events.ts new file mode 100644 index 0000000000..bba571d7c5 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.runs.$runId.events.ts @@ -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 +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 } + ); + } +); From 2e2632d5cce0f701ab6e82eabdc3906b0bd9f976 Mon Sep 17 00:00:00 2001 From: saadi Date: Tue, 11 Mar 2025 16:20:56 +0100 Subject: [PATCH 10/12] Add new MCP tool to list the logs for a run This enables some basic level of debugging capabilities in combinatio with the other MCP tools --- packages/cli-v3/src/dev/mcpServer.ts | 20 ++++++++++++++++++++ packages/core/src/v3/apiClient/index.ts | 12 ++++++++++++ 2 files changed, 32 insertions(+) diff --git a/packages/cli-v3/src/dev/mcpServer.ts b/packages/cli-v3/src/dev/mcpServer.ts index 1ddfa9ca3c..c63926e1df 100644 --- a/packages/cli-v3/src/dev/mcpServer.ts +++ b/packages/cli-v3/src/dev/mcpServer.ts @@ -182,6 +182,26 @@ server.tool( } ); +server.tool( + "get-run-logs", + "Retrieve the logs output of a task run.", + { + runId: z.string().describe("The ID of the task run to get"), + }, + async ({ runId }) => { + const result = await sdkApiClient.listRunEvents(runId); + + return { + content: [ + { + text: JSON.stringify(result, null, 2), + type: "text", + }, + ], + }; + } +); + const app = polka(); app.get("/sse", (_req, res) => { mcpTransport = new SSEServerTransport("/messages", res); diff --git a/packages/core/src/v3/apiClient/index.ts b/packages/core/src/v3/apiClient/index.ts index 873a0b3122..a10171d0ed 100644 --- a/packages/core/src/v3/apiClient/index.ts +++ b/packages/core/src/v3/apiClient/index.ts @@ -404,6 +404,18 @@ export class ApiClient { ); } + listRunEvents(runId: string, requestOptions?: ZodFetchOptions) { + return zodfetch( + z.any(), // TODO: define a proper schema for this + `${this.baseUrl}/api/v1/runs/${runId}/events`, + { + method: "GET", + headers: this.#getHeaders(false), + }, + mergeRequestOptions(this.defaultRequestOptions, requestOptions) + ); + } + addTags(runId: string, body: AddTagsRequestBody, requestOptions?: ZodFetchOptions) { return zodfetch( z.object({ message: z.string() }), From 603c903fbe4a04a9ceb208bc9a7405e390f7d4dd Mon Sep 17 00:00:00 2001 From: saadi Date: Tue, 11 Mar 2025 18:24:28 +0100 Subject: [PATCH 11/12] Add MCP tool to list all available tasks and enable fuzzy task matching This enables LLMs to figure out which task you are referring to, without needing to specify the full task ID --- packages/cli-v3/src/dev/mcpServer.ts | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/cli-v3/src/dev/mcpServer.ts b/packages/cli-v3/src/dev/mcpServer.ts index c63926e1df..8c4e57da34 100644 --- a/packages/cli-v3/src/dev/mcpServer.ts +++ b/packages/cli-v3/src/dev/mcpServer.ts @@ -1,11 +1,13 @@ +import polka from "polka"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { optional, union, z } from "zod"; +import { z } from "zod"; import { logger } from "../utilities/logger.js"; import { CliApiClient } from "../apiClient.js"; import { ApiClient, RunStatus } from "@trigger.dev/core/v3"; -import polka from "polka"; +import { eventBus } from "../utilities/eventBus.js"; +let allTaskIds: string[] = []; let projectRef: string; let dashboardUrl: string; // there is some overlap between `ApiClient` and `CliApiClient` which is not ideal @@ -20,6 +22,22 @@ const server = new McpServer({ version: "1.0.0", }); +// The `list-all-tasks` tool primarily helps to enable fuzzy matching of task IDs (names). +// This way, one doesn't need to specify the full task ID and rather let the LLM figure it out. +// This could be a good fit for the `resource` entity in MCP. +// Also, a custom `prompt` entity could be useful to instruct the LLM to prompt the user +// for selecting a task from a list of matching tasks, when the confidence for an exact match is low. +server.tool("list-all-tasks", "List all available task IDs in the worker.", async () => { + return { + content: [ + { + text: JSON.stringify(allTaskIds, null, 2), + type: "text", + }, + ], + }; +}); + server.tool( "trigger-task", "Trigger a task", @@ -213,6 +231,10 @@ app.post("/messages", (req, res) => { } }); +eventBus.on("backgroundWorkerInitialized", (worker) => { + allTaskIds = worker.manifest?.tasks.map((task) => task.id) ?? []; +}); + export const startMcpServer = async (options: { port: number; cliApiClient: CliApiClient; From 7d65aa929936e7383bcf3a9b4167097a6a03352a Mon Sep 17 00:00:00 2001 From: saadi Date: Tue, 11 Mar 2025 19:17:46 +0100 Subject: [PATCH 12/12] Describe the MCP server feature in the readme --- .cursor/mcp.json | 7 +++++++ packages/cli-v3/README.md | 31 +++++++++++++++++++++++++++++ packages/cli-v3/src/commands/dev.ts | 1 - 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .cursor/mcp.json diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 0000000000..96a6f73e4e --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,7 @@ +{ + "mcpServers": { + "trigger.dev": { + "url": "http://localhost:3333/sse" + } + } +} diff --git a/packages/cli-v3/README.md b/packages/cli-v3/README.md index de0e576f3e..d7fba79d54 100644 --- a/packages/cli-v3/README.md +++ b/packages/cli-v3/README.md @@ -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. diff --git a/packages/cli-v3/src/commands/dev.ts b/packages/cli-v3/src/commands/dev.ts index b286341d0b..b6c652d786 100644 --- a/packages/cli-v3/src/commands/dev.ts +++ b/packages/cli-v3/src/commands/dev.ts @@ -50,7 +50,6 @@ export function configureDevCommand(program: Command) { "--keep-tmp-files", "Keep temporary files after the dev session ends, helpful for debugging" ) - // TODO: add a more detailed description, maybe a pointer to the docs on how to use MCP .option("--mcp", "Start the MCP server") .option("--mcp-port", "The port to run the MCP server on", "3333") ).action(async (options) => {