Skip to content

Commit ce7420c

Browse files
committed
Initial scaffolding
1 parent e9cc7de commit ce7420c

File tree

9 files changed

+391
-21
lines changed

9 files changed

+391
-21
lines changed

packages/cli-v3/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,14 @@
7575
"dev": "tshy --watch",
7676
"test": "vitest",
7777
"test:e2e": "vitest --run -c ./e2e/vitest.config.ts",
78-
"update-version": "tsx ../../scripts/updateVersion.ts"
78+
"update-version": "tsx ../../scripts/updateVersion.ts",
79+
"install-mcp": "./install-mcp.sh",
80+
"inspector": "npx @modelcontextprotocol/inspector dist/esm/index.js mcp --log-file .mcp.log"
7981
},
8082
"dependencies": {
8183
"@clack/prompts": "^0.10.0",
8284
"@depot/cli": "0.0.1-cli.2.80.0",
83-
"@modelcontextprotocol/sdk": "^1.6.1",
85+
"@modelcontextprotocol/sdk": "^1.17.0",
8486
"@opentelemetry/api": "1.9.0",
8587
"@opentelemetry/api-logs": "0.203.0",
8688
"@opentelemetry/exporter-trace-otlp-http": "0.203.0",

packages/cli-v3/src/cli/index.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
import { Command } from "commander";
2+
import { configureAnalyzeCommand } from "../commands/analyze.js";
3+
import { configureDeployCommand } from "../commands/deploy.js";
24
import { configureDevCommand } from "../commands/dev.js";
35
import { configureInitCommand } from "../commands/init.js";
6+
import { configureListProfilesCommand } from "../commands/list-profiles.js";
47
import { configureLoginCommand } from "../commands/login.js";
58
import { configureLogoutCommand } from "../commands/logout.js";
9+
import { configurePreviewCommand } from "../commands/preview.js";
10+
import { configurePromoteCommand } from "../commands/promote.js";
11+
import { configureSwitchProfilesCommand } from "../commands/switch.js";
12+
import { configureUpdateCommand } from "../commands/update.js";
613
import { configureWhoamiCommand } from "../commands/whoami.js";
14+
import { configureMcpCommand } from "../commands/mcp.js";
715
import { COMMAND_NAME } from "../consts.js";
8-
import { configureListProfilesCommand } from "../commands/list-profiles.js";
9-
import { configureAnalyzeCommand } from "../commands/analyze.js";
10-
import { configureUpdateCommand } from "../commands/update.js";
1116
import { VERSION } from "../version.js";
12-
import { configureDeployCommand } from "../commands/deploy.js";
1317
import { installExitHandler } from "./common.js";
14-
import { configureWorkersCommand } from "../commands/workers/index.js";
15-
import { configureSwitchProfilesCommand } from "../commands/switch.js";
16-
import { configureTriggerTaskCommand } from "../commands/trigger.js";
17-
import { configurePromoteCommand } from "../commands/promote.js";
18-
import { configurePreviewCommand } from "../commands/preview.js";
1918

2019
export const program = new Command();
2120

@@ -36,7 +35,6 @@ configureSwitchProfilesCommand(program);
3635
configureUpdateCommand(program);
3736
configurePreviewCommand(program);
3837
configureAnalyzeCommand(program);
39-
// configureWorkersCommand(program);
40-
// configureTriggerTaskCommand(program);
38+
configureMcpCommand(program);
4139

4240
installExitHandler();
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3+
import { Command } from "commander";
4+
import { z } from "zod";
5+
import { CommonCommandOptions, commonOptions, wrapCommandAction } from "../cli/common.js";
6+
import { login } from "./login.js";
7+
import { performSearch } from "../mcp/mintlifyClient.js";
8+
import { logger } from "../utilities/logger.js";
9+
import { FileLogger } from "../mcp/logger.js";
10+
import { McpContext } from "../mcp/context.js";
11+
import { registerGetProjectDetailsTool } from "../mcp/tools.js";
12+
13+
const McpCommandOptions = CommonCommandOptions.extend({
14+
projectRef: z.string().optional(),
15+
logFile: z.string().optional(),
16+
});
17+
18+
export type McpCommandOptions = z.infer<typeof McpCommandOptions>;
19+
20+
export function configureMcpCommand(program: Command) {
21+
return commonOptions(
22+
program
23+
.command("mcp")
24+
.description("Run the MCP server")
25+
.option("-p, --project-ref <project ref>", "The project ref to use")
26+
.option("--log-file <log file>", "The file to log to")
27+
).action(async (options) => {
28+
wrapCommandAction("mcp", McpCommandOptions, options, async (opts) => {
29+
await mcpCommand(opts);
30+
});
31+
});
32+
}
33+
34+
export async function mcpCommand(options: McpCommandOptions) {
35+
logger.loggerLevel = "none";
36+
37+
const authorization = await login({
38+
embedded: true,
39+
silent: true,
40+
defaultApiUrl: options.apiUrl,
41+
profile: options.profile,
42+
});
43+
44+
if (!authorization.ok) {
45+
process.exitCode = 1;
46+
return;
47+
}
48+
49+
const server = new McpServer({
50+
name: "triggerdev",
51+
version: "1.0.0",
52+
description: "Trigger.dev MCP server. Search the Trigger.dev docs.",
53+
});
54+
55+
const fileLogger: FileLogger | undefined = options.logFile
56+
? new FileLogger(options.logFile, server)
57+
: undefined;
58+
59+
const context = new McpContext(server, {
60+
login: authorization,
61+
projectRef: options.projectRef,
62+
fileLogger,
63+
});
64+
65+
server.registerTool(
66+
"search_docs",
67+
{
68+
description:
69+
"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",
70+
inputSchema: {
71+
query: z.string(),
72+
},
73+
},
74+
async ({ query }) => {
75+
const results = await performSearch(query);
76+
return results;
77+
}
78+
);
79+
80+
registerGetProjectDetailsTool(context);
81+
82+
// Start receiving messages on stdin and sending messages on stdout
83+
const transport = new StdioServerTransport();
84+
await server.connect(transport);
85+
}

packages/cli-v3/src/install-mcp.sh

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/bin/bash
2+
3+
set -e # Exit on error
4+
5+
echo "🚀 Installing Trigger.dev MCP Server..."
6+
7+
# Get the absolute path to the node binary
8+
NODE_PATH=$(which node)
9+
if [ -z "$NODE_PATH" ]; then
10+
echo "❌ Error: Node.js not found in PATH"
11+
echo "Please ensure Node.js is installed and available in your PATH"
12+
exit 1
13+
fi
14+
15+
# Get the directory where this script is located
16+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17+
18+
# Construct the path to the CLI index.js file
19+
CLI_PATH="$SCRIPT_DIR/dist/esm/index.js"
20+
21+
# Construct the path to the MCP log file
22+
MCP_LOG_FILE="$SCRIPT_DIR/.mcp.log"
23+
24+
# Make sure the MCP log file exists
25+
touch "$MCP_LOG_FILE"
26+
27+
# Check if the CLI file exists
28+
if [ ! -f "$CLI_PATH" ]; then
29+
echo "❌ Error: CLI file not found at $CLI_PATH"
30+
echo "Make sure to build the CLI first with: pnpm run build"
31+
exit 1
32+
fi
33+
34+
# Ensure the CLI is executable
35+
chmod +x "$CLI_PATH"
36+
37+
echo "✅ Found Node.js at: $NODE_PATH"
38+
echo "✅ Found CLI at: $CLI_PATH"
39+
40+
# Claude Code configuration
41+
CLAUDE_CONFIG="$HOME/.claude.json"
42+
43+
echo "📁 Claude configuration file: $CLAUDE_CONFIG"
44+
45+
# Check if Claude config exists, create if it doesn't
46+
if [ ! -f "$CLAUDE_CONFIG" ]; then
47+
echo "📝 Creating new Claude configuration file..."
48+
echo '{"mcpServers": {}}' > "$CLAUDE_CONFIG"
49+
fi
50+
51+
# Use Node.js to manipulate the JSON
52+
echo "🔧 Updating Claude configuration..."
53+
54+
node -e "
55+
const fs = require('fs');
56+
const path = require('path');
57+
58+
const configPath = '$CLAUDE_CONFIG';
59+
const nodePath = '$NODE_PATH';
60+
const cliPath = '$CLI_PATH';
61+
const logFile = '$MCP_LOG_FILE';
62+
63+
try {
64+
// Read existing config
65+
let config;
66+
try {
67+
const configContent = fs.readFileSync(configPath, 'utf8');
68+
config = JSON.parse(configContent);
69+
} catch (error) {
70+
console.log('📝 Creating new configuration structure...');
71+
config = {};
72+
}
73+
74+
// Ensure mcpServers object exists
75+
if (!config.mcpServers) {
76+
config.mcpServers = {};
77+
}
78+
79+
// Add/update trigger.dev entry
80+
config.mcpServers['trigger'] = {
81+
command: nodePath,
82+
args: [cliPath, 'mcp', '--log-file', logFile]
83+
};
84+
85+
// Write back to file with proper formatting
86+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
87+
88+
console.log('✅ Successfully installed Trigger.dev MCP server to Claude Code');
89+
console.log('');
90+
console.log('📋 Configuration Details:');
91+
console.log(' • Config file:', configPath);
92+
console.log(' • Node.js path:', nodePath);
93+
console.log(' • CLI path:', cliPath);
94+
console.log('');
95+
console.log('🎉 Installation complete! You can now use Trigger.dev MCP commands in Claude Code.');
96+
console.log('💡 Try typing @ in Claude Code and select \"triggerdev\" to get started.');
97+
98+
} catch (error) {
99+
console.error('❌ Error updating Claude configuration:', error.message);
100+
process.exit(1);
101+
}
102+
"
103+
104+
echo ""
105+
echo "🔍 You can test the MCP server with:"
106+
echo " pnpm run inspector"

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2+
import { FileLogger } from "./logger.js";
3+
import { LoginResult } from "../utilities/session.js";
4+
5+
export type McpContextOptions = {
6+
login: LoginResult;
7+
projectRef?: string;
8+
fileLogger?: FileLogger;
9+
};
10+
11+
export class McpContext {
12+
public readonly server: McpServer;
13+
public readonly options: McpContextOptions;
14+
15+
constructor(server: McpServer, options: McpContextOptions) {
16+
this.server = server;
17+
this.options = options;
18+
}
19+
20+
get logger() {
21+
return this.options.fileLogger;
22+
}
23+
}

packages/cli-v3/src/mcp/logger.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2+
import { appendFileSync } from "node:fs";
3+
import util from "node:util";
4+
5+
export class FileLogger {
6+
private filePath: string;
7+
private server: McpServer;
8+
9+
constructor(filePath: string, server: McpServer) {
10+
this.filePath = filePath;
11+
this.server = server;
12+
}
13+
14+
log(message: string, ...args: unknown[]) {
15+
const logMessage = `[${new Date().toISOString()}][${this.formatServerInfo()}] ${message} - ${util.inspect(
16+
args,
17+
{
18+
depth: null,
19+
colors: false,
20+
}
21+
)}\n`;
22+
appendFileSync(this.filePath, logMessage);
23+
}
24+
25+
private formatServerInfo() {
26+
return `${this.formatClientName()} ${this.formatClientVersion()} ${this.formatClientCapabilities()}`;
27+
}
28+
29+
private formatClientName() {
30+
const clientName = this.server.server.getClientVersion()?.name;
31+
return `client=${clientName ?? "unknown"}`;
32+
}
33+
34+
private formatClientVersion() {
35+
const clientVersion = this.server.server.getClientVersion();
36+
37+
return `version=${clientVersion?.version ?? "unknown"}`;
38+
}
39+
40+
private formatClientCapabilities() {
41+
const clientCapabilities = this.server.server.getClientCapabilities();
42+
43+
const keys = Object.keys(clientCapabilities ?? {});
44+
45+
return `capabilities=${keys.join(",")}`;
46+
}
47+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
export async function performSearch(query: string) {
2+
const body = callToolBody("search", { query });
3+
4+
const response = await fetch("https://trigger.dev/docs/mcp", {
5+
method: "POST",
6+
headers: {
7+
"Content-Type": "application/json",
8+
Accept: "application/json, text/event-stream",
9+
"MCP-Protocol-Version": "2025-06-18",
10+
},
11+
body: JSON.stringify(body),
12+
});
13+
14+
const data = await parseResponse(response);
15+
return data;
16+
}
17+
18+
async function parseResponse(response: Response) {
19+
if (response.headers.get("content-type")?.includes("text/event-stream")) {
20+
return parseSSEResponse(response);
21+
} else {
22+
return parseJSONResponse(response);
23+
}
24+
}
25+
26+
async function parseJSONResponse(response: Response) {
27+
const data = await response.json();
28+
return data;
29+
}
30+
31+
// Get the first data: event and return the parsed JSON of the event
32+
async function parseSSEResponse(response: Response) {
33+
const reader = response.body?.getReader();
34+
const decoder = new TextDecoder();
35+
36+
if (!reader) {
37+
throw new Error("No reader found");
38+
}
39+
40+
let buffer = "";
41+
42+
while (true) {
43+
const { value, done } = await reader.read();
44+
if (done) throw new Error("SSE stream closed before data arrived");
45+
46+
buffer += decoder.decode(value, { stream: true });
47+
const events = buffer.split("\n\n"); // SSE delimiter
48+
buffer = events.pop()!; // keep incomplete
49+
50+
for (const evt of events) {
51+
for (const line of evt.split("\n")) {
52+
if (line.startsWith("data:")) {
53+
const json = line.slice(5).trim();
54+
return JSON.parse(json); // ✅ got it
55+
}
56+
}
57+
}
58+
}
59+
60+
throw new Error("No data: event found");
61+
}
62+
63+
function callToolBody(tool: string, args: Record<string, unknown>) {
64+
return {
65+
jsonrpc: "2.0",
66+
id: 1,
67+
method: "tools/call",
68+
params: {
69+
name: tool,
70+
arguments: args,
71+
},
72+
};
73+
}

0 commit comments

Comments
 (0)