deploy-template
Search and deploy Railway templates using fuzzy search. Automatically deploy selected templates to your project and environment, streamlining setup and configuration.
Instructions
Search and deploy Railway templates. This tool will search for templates using fuzzy search and automatically deploy the selected template to the current Railway project and environment.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| searchQuery | Yes | Search query to filter templates by name, description, or category | |
| teamId | No | The ID of the team (optional) | |
| templateIndex | No | Index of the template to deploy (required if multiple templates found) | |
| workspacePath | Yes | The path to the workspace to deploy the template to |
Implementation Reference
- src/tools/deploy-template.ts:40-154 (handler)Main execution logic for the deploy-template tool. Searches templates using fuzzy search, lists if multiple, selects by index, links to current project/env, and deploys via Railway GraphQL API.handler: async ({ workspacePath, searchQuery, templateIndex, teamId, }: DeployTemplateOptions) => { try { // Search and list templates const { templates, filteredCount, totalCount } = await searchAndListTemplates({ searchQuery }); if (templates.length === 0) { return createToolResponse( `🔍 No templates found matching "${searchQuery}".\n\n` + `**Total templates available:** ${totalCount}\n\n` + `**Suggestions:**\n` + `• Try a different search term\n` + `• Use broader keywords\n` + `• Check your internet connection`, ); } // If multiple templates found and no index specified, show the list if (templates.length > 1 && templateIndex === undefined) { const templateList = templates .map((template: Template, index: number) => { const verifiedBadge = template.isVerified ? "verified" : "unverified"; return ( `${index + 1}. **${template.name}** - (${verifiedBadge})\n` + ` ID: \`${template.id}\`\n` + ` Description: ${template.description || "No description available"}\n` + ` Category: ${template.category}\n` + ` Active Projects: ${template.activeProjects} | Health: ${template.health} | Payout: ${template.totalPayout}` ); }) .join("\n\n"); return createToolResponse( `🔍 Multiple templates found matching "${searchQuery}":\n\n` + `**Showing ${filteredCount} of ${totalCount} templates:**\n\n` + templateList + `\n\n**Please specify which template to deploy by providing:**\n` + `• templateIndex: [1-${templates.length}]`, ); } // Get the template to deploy let templateToDeploy: Template; if (templates.length === 1) { templateToDeploy = templates[0]; } else if (templateIndex !== undefined) { if (templateIndex < 1 || templateIndex > templates.length) { return createToolResponse( `❌ Invalid template index: ${templateIndex}\n\n` + `**Valid range:** 1-${templates.length}\n\n` + `Please provide a valid template index.`, ); } templateToDeploy = templates[templateIndex - 1]; } else { // This shouldn't happen, but just in case return createToolResponse( "❌ Unexpected error: Multiple templates found but no index specified.", ); } // Get current project and environment IDs from Railway context const projectResult = await getLinkedProjectInfo({ workspacePath }); if (!projectResult.success || !projectResult.project) { return createToolResponse( "❌ No Railway project is linked to this workspace.\n\n" + "**Next Steps:**\n" + "• Run `railway link` to connect to a project\n" + "• Or use the `create-project-and-link` tool to create a new project", ); } const currentProjectId = projectResult.project.id; const currentEnvironmentId = await getCurrentEnvironmentId({ workspacePath, }); // Deploy the template const result = await deployTemplate({ environmentId: currentEnvironmentId, projectId: currentProjectId, serializedConfig: templateToDeploy.serializedConfig, templateId: templateToDeploy.id, teamId, }); return createToolResponse( `✅ Successfully deployed Railway template:\n\n` + `**Template:** ${templateToDeploy.name}\n` + `**Template ID:** ${templateToDeploy.id}\n` + `**Project:** ${projectResult.project.name} (${currentProjectId})\n` + `**Environment:** ${currentEnvironmentId}\n` + `**Workflow ID:** ${result.workflowId}\n\n` + `The template has been deployed successfully to the current Railway project and environment.`, ); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; return createToolResponse( "❌ Failed to search/deploy Railway template\n\n" + `**Error:** ${errorMessage}\n\n` + "**Next Steps:**\n" + "• Check your internet connection\n" + "• Verify that Railway's API is accessible\n" + "• Make sure you're authenticated with Railway (`railway login`)", ); } },
- src/tools/deploy-template.ts:23-39 (schema)Zod input schema defining parameters for the deploy-template tool: workspacePath, searchQuery, optional templateIndex and teamId.inputSchema: { workspacePath: z .string() .describe("The path to the workspace to deploy the template to"), searchQuery: z .string() .describe( "Search query to filter templates by name, description, or category", ), templateIndex: z .number() .optional() .describe( "Index of the template to deploy (required if multiple templates found)", ), teamId: z.string().optional().describe("The ID of the team (optional)"), },
- src/tools/index.ts:5-5 (registration)Export of the deployTemplateTool for inclusion in the tools index, likely used for MCP tool registration.export { deployTemplateTool } from "./deploy-template";
- src/api/deploy-template.ts:137-194 (helper)Helper function that performs the actual GraphQL mutation to deploy a template to a Railway project/environment.export const deployTemplate = async ({ environmentId, projectId, serializedConfig, templateId, teamId, }: { environmentId: string; projectId: string; serializedConfig: SerializedTemplateConfig; templateId: string; teamId?: string; }): Promise<TemplateDeployResponse> => { const query = ` mutation deployTemplate($environmentId: String, $projectId: String, $templateId: String!, $teamId: String, $serializedConfig: SerializedTemplateConfig!) { templateDeployV2(input: { environmentId: $environmentId, projectId: $projectId, templateId: $templateId, teamId: $teamId, serializedConfig: $serializedConfig }) { projectId workflowId } } `; try { const token = getRailwayAuthToken(); const client = new GraphQLClient( "https://backboard.railway.com/graphql/v2", { headers: { Authorization: `Bearer ${token}`, "x-source": "railway-mcp-server", }, }, ); const data = await client.request<{ templateDeployV2: TemplateDeployResponse; }>(query, { environmentId, projectId, templateId, teamId, serializedConfig, }); return data.templateDeployV2; } catch (error: unknown) { if (error instanceof Error) { throw new Error(`Failed to deploy template: ${error.message}`); } throw new Error("Failed to deploy template: Unknown error"); } };
- src/api/deploy-template.ts:30-135 (helper)Helper function to query Railway GraphQL for templates, sort them, and perform fuzzy search filtering.export const searchAndListTemplates = async ({ searchQuery, }: { searchQuery?: string; }): Promise<{ templates: Template[]; filteredCount: number; totalCount: number; }> => { const query = ` query { templates { edges { node { id name description category serializedConfig activeProjects health totalPayout isVerified } } } } `; try { const client = new GraphQLClient( "https://backboard.railway.com/graphql/v2", { headers: { "x-source": "railway-mcp-server", }, }, ); const data = await client.request<{ templates: { edges: Array<{ node: Template; }>; }; }>(query); const templates = data.templates.edges.map( (edge) => edge.node, ) as Template[]; const totalCount = templates.length; // Sort templates based on the specified criteria (verified templates first, then by totalPayout, then by activeProjects, then by health) const sortedTemplates = templates.sort((a: Template, b: Template) => { if (a.isVerified && !b.isVerified) return -1; if (!a.isVerified && b.isVerified) return 1; if (a.totalPayout !== b.totalPayout) { return (b.totalPayout || 0) - (a.totalPayout || 0); } if (a.activeProjects !== b.activeProjects) { return (b.activeProjects || 0) - (a.activeProjects || 0); } if (a.health !== b.health) { return (b.health || 0) - (a.health || 0); } // If all criteria are equal, maintain original order return 0; }); // If no search query provided, return all sorted templates if (!searchQuery || searchQuery.trim() === "") { return { templates: sortedTemplates, filteredCount: totalCount, totalCount, }; } // Configure Fuse.js for fuzzy search const fuse = new Fuse(sortedTemplates, { keys: ["name", "description", "category"], threshold: 0.3, // Lower threshold = more strict matching includeScore: true, includeMatches: true, }); // Perform fuzzy search const searchResults = fuse.search(searchQuery.trim()); const filteredTemplates = searchResults.map((result) => result.item); return { templates: filteredTemplates, filteredCount: filteredTemplates.length, totalCount, }; } catch (error: unknown) { if (error instanceof Error) { throw new Error(`Failed to search Railway templates: ${error.message}`); } throw new Error("Failed to search Railway templates: Unknown error"); } };