Skip to main content
Glama
index.ts18 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { isAuthenticated } from "./config.js"; import * as api from "./api.js"; const server = new McpServer({ name: "nexus", version: "0.1.0", }); // ============================================================================ // Project Tools // ============================================================================ server.tool( "nexus_list_projects", "List all projects in the organization with optional filtering by status or assignment", { status: z .string() .optional() .describe("Filter by project status (e.g., IN_DEVELOPMENT, LIVE)"), assignedTo: z .string() .optional() .describe("Filter by assigned user ID"), limit: z .number() .optional() .describe("Maximum number of projects to return"), }, async ({ status, assignedTo, limit }) => { const projects = await api.listProjects({ status, assignedTo, limit }); return { content: [ { type: "text", text: JSON.stringify(projects, null, 2), }, ], }; } ); server.tool( "nexus_get_project", "Get detailed information about a specific project", { projectId: z .string() .describe("Project ID or slug"), }, async ({ projectId }) => { const project = await api.getProject(projectId); return { content: [ { type: "text", text: JSON.stringify(project, null, 2), }, ], }; } ); server.tool( "nexus_create_project", "Create a new project in Nexus", { name: z.string().describe("Project name"), description: z.string().optional().describe("Project description"), url: z.string().optional().describe("Project URL"), templateId: z.string().optional().describe("Template ID to base project on"), deadline: z.string().optional().describe("Project deadline (ISO date)"), screenshotBase64: z .string() .optional() .describe("Base64-encoded screenshot image"), screenshotUrl: z.string().optional().describe("URL to screenshot image"), status: z.string().optional().describe("Initial project status"), }, async (params) => { const project = await api.createProject(params); return { content: [ { type: "text", text: `Project created successfully:\n${JSON.stringify(project, null, 2)}`, }, ], }; } ); server.tool( "nexus_update_project", "Update a project's details including test credentials", { projectId: z.string().describe("Project ID or slug"), name: z.string().optional().describe("Updated project name"), description: z.string().optional().describe("Updated description"), url: z.string().optional().describe("Updated project URL"), testCredentials: z .record(z.unknown()) .optional() .describe("Test credentials as JSON object"), setupData: z .record(z.unknown()) .optional() .describe("Setup data as JSON object"), featureCompletion: z .number() .min(0) .max(100) .optional() .describe("Feature completion percentage (0-100)"), productionReadiness: z .number() .min(0) .max(100) .optional() .describe("Production readiness percentage (0-100)"), previewImage: z.string().optional().describe("Preview image URL"), }, async ({ projectId, ...data }) => { const project = await api.updateProject(projectId, data); return { content: [ { type: "text", text: `Project updated:\n${JSON.stringify(project, null, 2)}`, }, ], }; } ); server.tool( "nexus_update_project_status", "Update a project's status", { projectId: z.string().describe("Project ID or slug"), status: z .string() .describe( "New status: IDEA, CONCEPT_BUCKET, READY_FOR_DEV, IN_DEVELOPMENT, READY_FOR_BUSINESS_REVIEW, READY_FOR_QA, IN_QA, PAYMENT_INTEGRATION, ON_HOLD, READY_FOR_ONBOARDING, WAITING_FOR_API_KEYS, LIVE_PREPARATION, LIVE, ARCHIVED" ), }, async ({ projectId, status }) => { const project = await api.updateProjectStatus(projectId, status); return { content: [ { type: "text", text: `Project status updated to ${status}:\n${JSON.stringify(project, null, 2)}`, }, ], }; } ); // ============================================================================ // Bug/Card Tools // ============================================================================ server.tool( "nexus_list_bugs", "Get all bugs/cards for a project with optional filtering", { projectId: z.string().describe("Project ID or slug"), status: z .string() .optional() .describe( "Filter by status: BACKLOG, TODO, IN_AI_DEV, IN_DEV, IN_REVIEW, NEEDS_VERIFICATION, DONE, BLOCKED, CANCELLED" ), severity: z .string() .optional() .describe("Filter by severity: LOW, MEDIUM, HIGH, CRITICAL"), assignedToAI: z .boolean() .optional() .describe("Filter bugs assigned to AI"), limit: z.number().optional().describe("Maximum number of bugs to return"), }, async ({ projectId, status, severity, assignedToAI, limit }) => { const bugs = await api.listBugs(projectId, { status, severity, assignedToAI, limit, }); return { content: [ { type: "text", text: JSON.stringify(bugs, null, 2), }, ], }; } ); server.tool( "nexus_create_bug", "Create a new bug/issue card in a project", { projectId: z.string().describe("Project ID or slug"), title: z.string().describe("Bug title"), description: z.string().describe("Bug description"), severity: z .enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]) .describe("Bug severity"), steps: z.string().optional().describe("Steps to reproduce"), expected: z.string().optional().describe("Expected behavior"), actual: z.string().optional().describe("Actual behavior"), environment: z.string().optional().describe("Environment info"), screenshotUrl: z.string().optional().describe("Screenshot URL"), priority: z .enum(["LOW", "MEDIUM", "HIGH", "URGENT"]) .optional() .describe("Priority level"), labels: z.array(z.string()).optional().describe("Labels/tags"), assigneeId: z.string().optional().describe("User ID to assign"), }, async ({ projectId, ...data }) => { const bug = await api.createBug(projectId, data); return { content: [ { type: "text", text: `Bug created:\n${JSON.stringify(bug, null, 2)}`, }, ], }; } ); server.tool( "nexus_update_card", "Update a card's status, assignee, or other fields", { cardId: z.string().describe("Card ID"), title: z.string().optional().describe("Updated title"), description: z.string().optional().describe("Updated description"), status: z .string() .optional() .describe( "New status: BACKLOG, TODO, IN_AI_DEV, IN_DEV, IN_REVIEW, NEEDS_VERIFICATION, DONE, BLOCKED, CANCELLED" ), priority: z .string() .optional() .describe("New priority: LOW, MEDIUM, HIGH, URGENT"), labels: z.array(z.string()).optional().describe("Updated labels"), assigneeId: z.string().optional().describe("New assignee user ID"), }, async ({ cardId, ...data }) => { const card = await api.updateCard(cardId, data); return { content: [ { type: "text", text: `Card updated:\n${JSON.stringify(card, null, 2)}`, }, ], }; } ); server.tool( "nexus_add_comment", "Add a comment to a card", { cardId: z.string().describe("Card ID"), content: z.string().describe("Comment content (markdown supported)"), mentions: z .array(z.string()) .optional() .describe("User IDs to @mention"), }, async ({ cardId, content, mentions }) => { const comment = await api.addComment(cardId, { content, mentions }); return { content: [ { type: "text", text: `Comment added:\n${JSON.stringify(comment, null, 2)}`, }, ], }; } ); // ============================================================================ // Template Tools // ============================================================================ server.tool( "nexus_list_templates", "List available project templates", { kind: z .string() .optional() .describe("Filter by kind: UI, API, FULLSTACK"), tags: z.array(z.string()).optional().describe("Filter by tags"), }, async ({ kind, tags }) => { const templates = await api.listTemplates({ kind, tags }); return { content: [ { type: "text", text: JSON.stringify(templates, null, 2), }, ], }; } ); server.tool( "nexus_create_template", "Create a new project template", { name: z.string().describe("Template name"), slug: z.string().describe("URL-friendly slug (must be unique)"), description: z.string().optional().describe("Template description"), kind: z.enum(["UI", "API", "FULLSTACK"]).describe("Template kind"), githubRepoUrl: z.string().optional().describe("GitHub repository URL"), previewImage: z.string().optional().describe("Preview image URL"), tags: z.array(z.string()).optional().describe("Tags for categorization"), promptsJson: z .record(z.unknown()) .optional() .describe("AI prompts configuration"), }, async (params) => { const template = await api.createTemplate(params); return { content: [ { type: "text", text: `Template created:\n${JSON.stringify(template, null, 2)}`, }, ], }; } ); // ============================================================================ // Milestone Tools // ============================================================================ server.tool( "nexus_list_milestones", "List milestones with optional date range and type filtering", { startDate: z.string().optional().describe("Filter from date (ISO format)"), endDate: z.string().optional().describe("Filter to date (ISO format)"), type: z .string() .optional() .describe("Filter by type: GENERAL, ONBOARDING, LAUNCH, REVIEW, DEADLINE"), completed: z.boolean().optional().describe("Filter by completion status"), }, async ({ startDate, endDate, type, completed }) => { const milestones = await api.listMilestones({ startDate, endDate, type, completed, }); return { content: [ { type: "text", text: JSON.stringify(milestones, null, 2), }, ], }; } ); server.tool( "nexus_create_milestone", "Create a new milestone", { title: z.string().describe("Milestone title"), date: z.string().describe("Milestone date (ISO format)"), description: z.string().optional().describe("Description"), type: z .string() .optional() .describe("Type: GENERAL, ONBOARDING, LAUNCH, REVIEW, DEADLINE"), priority: z.string().optional().describe("Priority: HIGH, MEDIUM, LOW"), color: z.string().optional().describe("Color for calendar display"), projectIds: z .array(z.string()) .optional() .describe("Project IDs to link"), tasks: z .array(z.object({ title: z.string(), order: z.number().optional() })) .optional() .describe("Subtasks to create"), businessEntity: z .string() .optional() .describe("Business entity (for ONBOARDING type)"), paymentProvider: z .string() .optional() .describe("Payment provider (for ONBOARDING type)"), }, async (params) => { const milestone = await api.createMilestone(params); return { content: [ { type: "text", text: `Milestone created:\n${JSON.stringify(milestone, null, 2)}`, }, ], }; } ); server.tool( "nexus_update_milestone", "Update a milestone", { milestoneId: z.string().describe("Milestone ID"), title: z.string().optional().describe("Updated title"), description: z.string().optional().describe("Updated description"), date: z.string().optional().describe("Updated date (ISO format)"), completed: z.boolean().optional().describe("Mark as complete/incomplete"), addProjects: z .array(z.string()) .optional() .describe("Project IDs to add"), removeProjects: z .array(z.string()) .optional() .describe("Project IDs to remove"), }, async ({ milestoneId, ...data }) => { const milestone = await api.updateMilestone(milestoneId, data); return { content: [ { type: "text", text: `Milestone updated:\n${JSON.stringify(milestone, null, 2)}`, }, ], }; } ); server.tool( "nexus_update_milestone_task", "Update a milestone subtask", { milestoneId: z.string().describe("Milestone ID"), taskId: z.string().describe("Task ID"), title: z.string().optional().describe("Updated title"), completed: z.boolean().optional().describe("Mark as complete/incomplete"), }, async ({ milestoneId, taskId, ...data }) => { const task = await api.updateMilestoneTask(milestoneId, taskId, data); return { content: [ { type: "text", text: `Task updated:\n${JSON.stringify(task, null, 2)}`, }, ], }; } ); // ============================================================================ // Concept Tools // ============================================================================ server.tool( "nexus_list_concepts", "List product concepts in the organization", { status: z .string() .optional() .describe( "Filter by status: GENERATING, DRAFT, SELECTED, RESEARCHING, REVIEWED, APPROVED, PROMOTED, ARCHIVED" ), templateId: z.string().optional().describe("Filter by template ID"), }, async ({ status, templateId }) => { const concepts = await api.listConcepts({ status, templateId }); return { content: [ { type: "text", text: JSON.stringify(concepts, null, 2), }, ], }; } ); server.tool( "nexus_get_concept", "Get detailed information about a concept including PRD", { conceptId: z.string().describe("Concept ID or slug"), }, async ({ conceptId }) => { const concept = await api.getConcept(conceptId); return { content: [ { type: "text", text: JSON.stringify(concept, null, 2), }, ], }; } ); server.tool( "nexus_create_concept", "Create a new product concept", { name: z.string().describe("Concept name"), description: z.string().describe("Brief description"), problemStatement: z.string().optional().describe("Problem being solved"), solution: z.string().optional().describe("Proposed solution"), businessModel: z.string().optional().describe("Business model description"), targetAudience: z.string().optional().describe("Target audience"), templateId: z.string().optional().describe("Template to associate"), }, async (params) => { const concept = await api.createConcept(params); return { content: [ { type: "text", text: `Concept created:\n${JSON.stringify(concept, null, 2)}`, }, ], }; } ); server.tool( "nexus_update_concept", "Update a concept", { conceptId: z.string().describe("Concept ID"), name: z.string().optional().describe("Updated name"), description: z.string().optional().describe("Updated description"), status: z.string().optional().describe("New status"), problemStatement: z.string().optional().describe("Updated problem statement"), solution: z.string().optional().describe("Updated solution"), prdJson: z.record(z.unknown()).optional().describe("Updated PRD document"), }, async ({ conceptId, ...data }) => { const concept = await api.updateConcept(conceptId, data); return { content: [ { type: "text", text: `Concept updated:\n${JSON.stringify(concept, null, 2)}`, }, ], }; } ); server.tool( "nexus_promote_concept", "Promote a concept to a full project", { conceptId: z.string().describe("Concept ID to promote"), projectName: z.string().optional().describe("Override project name"), }, async ({ conceptId, projectName }) => { const result = await api.promoteConcept(conceptId, { projectName }); return { content: [ { type: "text", text: `Concept promoted to project:\n${JSON.stringify(result.project, null, 2)}`, }, ], }; } ); // ============================================================================ // Server Startup // ============================================================================ export async function startServer(): Promise<void> { if (!isAuthenticated()) { console.error("Error: Not authenticated."); console.error("Run: nexus-mcp login"); console.error("Or set NEXUS_API_KEY environment variable"); process.exit(1); } const transport = new StdioServerTransport(); await server.connect(transport); console.error("Nexus MCP Server running on stdio"); } // Run if executed directly const isMainModule = import.meta.url === `file://${process.argv[1]}`; if (isMainModule) { startServer().catch((error) => { console.error("Fatal error:", error); process.exit(1); }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/iamserge/nexus-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server