#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from "@modelcontextprotocol/sdk/types.js";
import { GraphQLClient } from "graphql-request";
import { z } from "zod";
// Environment variables validation
const envSchema = z.object({
RAILWAY_TOKEN: z.string().min(1, "RAILWAY_TOKEN is required"),
});
const env = envSchema.parse(process.env);
// Railway GraphQL API client
const railway = new GraphQLClient("https://backboard.railway.app/graphql/v2", {
headers: {
authorization: `Bearer ${env.RAILWAY_TOKEN}`,
},
});
// GraphQL queries and mutations
const QUERIES = {
GET_PROJECTS: `
query GetProjects {
projects {
edges {
node {
id
name
description
createdAt
updatedAt
isPublic
prDeploys
prForks
services {
edges {
node {
id
name
icon
createdAt
updatedAt
deployments {
edges {
node {
id
status
createdAt
updatedAt
url
staticUrl
meta
}
}
}
}
}
}
}
}
}
}
`,
GET_PROJECT: `
query GetProject($projectId: String!) {
project(id: $projectId) {
id
name
description
createdAt
updatedAt
isPublic
services {
edges {
node {
id
name
icon
createdAt
updatedAt
deployments {
edges {
node {
id
status
createdAt
updatedAt
url
staticUrl
meta
}
}
}
}
}
}
}
}
`,
GET_DEPLOYMENT_LOGS: `
query GetDeploymentLogs($deploymentId: String!) {
deployment(id: $deploymentId) {
id
status
createdAt
updatedAt
buildLogs
deployLogs
meta
}
}
`,
GET_SERVICE: `
query GetService($serviceId: String!) {
service(id: $serviceId) {
id
name
icon
createdAt
updatedAt
deployments {
edges {
node {
id
status
createdAt
updatedAt
url
staticUrl
meta
}
}
}
variables {
edges {
node {
id
name
value
}
}
}
}
}
`,
REDEPLOY_SERVICE: `
mutation RedeployService($serviceId: String!) {
serviceRedeploy(input: {serviceId: $serviceId}) {
deployment {
id
status
createdAt
updatedAt
}
}
}
`,
UPDATE_SERVICE_VARIABLE: `
mutation UpdateServiceVariable($serviceId: String!, $name: String!, $value: String!) {
variableUpsert(input: {serviceId: $serviceId, name: $name, value: $value}) {
variable {
id
name
value
}
}
}
`,
DELETE_SERVICE_VARIABLE: `
mutation DeleteServiceVariable($serviceId: String!, $name: String!) {
variableDelete(input: {serviceId: $serviceId, name: $name}) {
variable {
id
name
}
}
}
`,
DEPLOYMENT_CANCEL: `
mutation CancelDeployment($deploymentId: String!) {
deploymentCancel(input: {deploymentId: $deploymentId}) {
deployment {
id
status
updatedAt
}
}
}
`,
DEPLOYMENT_RESTART: `
mutation RestartDeployment($deploymentId: String!) {
deploymentRestart(input: {deploymentId: $deploymentId}) {
deployment {
id
status
updatedAt
}
}
}
`
};
// MCP Server setup
const server = new Server(
{
name: "railway-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "get_projects",
description: "Get all Railway projects and their services",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
{
name: "get_project",
description: "Get details of a specific Railway project",
inputSchema: {
type: "object",
properties: {
projectId: {
type: "string",
description: "The Railway project ID",
},
},
required: ["projectId"],
},
},
{
name: "get_service",
description: "Get details of a specific Railway service including deployments and variables",
inputSchema: {
type: "object",
properties: {
serviceId: {
type: "string",
description: "The Railway service ID",
},
},
required: ["serviceId"],
},
},
{
name: "get_deployment_logs",
description: "Get logs for a specific deployment",
inputSchema: {
type: "object",
properties: {
deploymentId: {
type: "string",
description: "The Railway deployment ID",
},
},
required: ["deploymentId"],
},
},
{
name: "redeploy_service",
description: "Trigger a new deployment for a service",
inputSchema: {
type: "object",
properties: {
serviceId: {
type: "string",
description: "The Railway service ID to redeploy",
},
},
required: ["serviceId"],
},
},
{
name: "cancel_deployment",
description: "Cancel a running deployment",
inputSchema: {
type: "object",
properties: {
deploymentId: {
type: "string",
description: "The Railway deployment ID to cancel",
},
},
required: ["deploymentId"],
},
},
{
name: "restart_deployment",
description: "Restart a deployment",
inputSchema: {
type: "object",
properties: {
deploymentId: {
type: "string",
description: "The Railway deployment ID to restart",
},
},
required: ["deploymentId"],
},
},
{
name: "update_service_variable",
description: "Update or create an environment variable for a service",
inputSchema: {
type: "object",
properties: {
serviceId: {
type: "string",
description: "The Railway service ID",
},
name: {
type: "string",
description: "The environment variable name",
},
value: {
type: "string",
description: "The environment variable value",
},
},
required: ["serviceId", "name", "value"],
},
},
{
name: "delete_service_variable",
description: "Delete an environment variable from a service",
inputSchema: {
type: "object",
properties: {
serviceId: {
type: "string",
description: "The Railway service ID",
},
name: {
type: "string",
description: "The environment variable name to delete",
},
},
required: ["serviceId", "name"],
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "get_projects": {
const result = await railway.request(QUERIES.GET_PROJECTS);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "get_project": {
const { projectId } = args as { projectId: string };
const result = await railway.request(QUERIES.GET_PROJECT, { projectId });
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "get_service": {
const { serviceId } = args as { serviceId: string };
const result = await railway.request(QUERIES.GET_SERVICE, { serviceId });
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "get_deployment_logs": {
const { deploymentId } = args as { deploymentId: string };
const result = await railway.request(QUERIES.GET_DEPLOYMENT_LOGS, { deploymentId });
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "redeploy_service": {
const { serviceId } = args as { serviceId: string };
const result = await railway.request(QUERIES.REDEPLOY_SERVICE, { serviceId });
return {
content: [
{
type: "text",
text: `✅ Service redeployed successfully:\n${JSON.stringify(result, null, 2)}`,
},
],
};
}
case "cancel_deployment": {
const { deploymentId } = args as { deploymentId: string };
const result = await railway.request(QUERIES.DEPLOYMENT_CANCEL, { deploymentId });
return {
content: [
{
type: "text",
text: `✅ Deployment canceled successfully:\n${JSON.stringify(result, null, 2)}`,
},
],
};
}
case "restart_deployment": {
const { deploymentId } = args as { deploymentId: string };
const result = await railway.request(QUERIES.DEPLOYMENT_RESTART, { deploymentId });
return {
content: [
{
type: "text",
text: `✅ Deployment restarted successfully:\n${JSON.stringify(result, null, 2)}`,
},
],
};
}
case "update_service_variable": {
const { serviceId, name, value } = args as {
serviceId: string;
name: string;
value: string;
};
const result = await railway.request(QUERIES.UPDATE_SERVICE_VARIABLE, {
serviceId,
name,
value,
});
return {
content: [
{
type: "text",
text: `✅ Environment variable '${name}' updated successfully:\n${JSON.stringify(result, null, 2)}`,
},
],
};
}
case "delete_service_variable": {
const { serviceId, name } = args as { serviceId: string; name: string };
const result = await railway.request(QUERIES.DELETE_SERVICE_VARIABLE, {
serviceId,
name,
});
return {
content: [
{
type: "text",
text: `✅ Environment variable '${name}' deleted successfully:\n${JSON.stringify(result, null, 2)}`,
},
],
};
}
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new McpError(ErrorCode.InternalError, `Railway API error: ${errorMessage}`);
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Railway MCP server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});