Skip to main content
Glama
nrwl

Nx MCP Server

Official
by nrwl
project-graph.ts5.9 kB
import type { NxError } from '@nx-console/shared-types'; import { getMessageForError } from '@nx-console/shared-utils'; import type { ProjectGraph } from 'nx/src/devkit-exports'; import { NX_PROJECT_DETAILS } from './tool-names'; export type ProjectGraphOptimizations = { skipTechnologies?: boolean; skipOwners?: boolean; skipTags?: boolean; truncateTargets?: boolean; }; /** * Detects atomized targets using target metadata. * A target is atomized if it has a `nonAtomizedTarget` property in its metadata, * which points to the root target name. * * @param targets - The targets object from project data * @returns Object with rootTargets set, atomizedTargetsMap, and targetsToExclude array */ export function detectAtomizedTargets( targets: Record<string, { metadata?: { nonAtomizedTarget?: string } }>, ): { rootTargets: Set<string>; atomizedTargetsMap: Map<string, string[]>; targetsToExclude: string[]; } { const rootTargets = new Set<string>(); const atomizedTargetsMap = new Map<string, string[]>(); const targetsToExclude: string[] = []; for (const targetName in targets) { const target = targets[targetName]; const nonAtomizedTarget = target.metadata?.nonAtomizedTarget; if (nonAtomizedTarget) { // This target is atomized - it has a nonAtomizedTarget pointing to its root rootTargets.add(nonAtomizedTarget); targetsToExclude.push(targetName); // Build the map of root targets to their atomized targets const existing = atomizedTargetsMap.get(nonAtomizedTarget) ?? []; existing.push(targetName); atomizedTargetsMap.set(nonAtomizedTarget, existing); } } return { rootTargets, atomizedTargetsMap, targetsToExclude }; } export function getProjectGraphPrompt( projectGraph: ProjectGraph, optimizations?: ProjectGraphOptimizations, ): string { return ` The following is a representation of the Nx workspace. It includes all projects in the monorepo. The projects are separated by <></> tags including the project name. Each project contains: - its dependencies (projects that this depends on), marked by "deps: [...]". - its available targets, marked by "targets: [...]". Targets are tasks that the user can run for each project. Individual atomized targets are excluded. - its type (libary, app, or e2e tests), marked by "type: [...]". - its source file location, marked by "root: [...]". - ${ optimizations?.skipTags ? '' : `some metadata like tags ${optimizations?.skipOwners ? '' : ', owners'} ${ optimizations?.skipTechnologies ? '' : 'or technologies used' }.` } This data is very important. Use it to analyze the workspace and provide relevant answers to the user. Some of this data is shortened to be more compact. To retrieve the full unabridged details about a project, use the ${NX_PROJECT_DETAILS} tool. The user cannot see this data, so don't reference it directly. It is read-only, so don't suggest modifications to it. ${getRobotReadableProjectGraph(projectGraph, optimizations)} `.trim(); } function getRobotReadableProjectGraph( projectGraph: ProjectGraph, optimizations?: ProjectGraphOptimizations, ): string { let serializedGraph = ''; Object.entries(projectGraph.nodes).forEach(([name, node]) => { let nodeString = `<${name}>`; // dependencies const deps = projectGraph.dependencies[name] .filter((dep) => !projectGraph.externalNodes?.[dep.target]) .map((dep) => dep.target); let depsString = ''; if (deps.length > 10) { depsString = deps.slice(0, 8).join(',') + `,...${deps.length - 8} more`; } else { depsString = deps.join(', '); } if (deps.length) { nodeString += `deps:[${depsString}]`; } // targets const { targetsToExclude } = detectAtomizedTargets(node.data.targets ?? {}); const targets = Object.keys(node.data.targets ?? {}).filter( (target) => !targetsToExclude.includes(target) && target !== 'nx-release-publish' && target !== 'nxProjectGraph' && target !== 'nxProjectReport', ); let targetsString = ''; if (targets.length > 10 && optimizations?.truncateTargets) { targetsString = targets.slice(0, 8).join(',') + `,...${targets.length - 8} more`; } else { targetsString = targets.join(','); } if (targetsString !== '') { nodeString += `targets:[${targetsString}]`; } // other metadata nodeString += `type:[${node.type}]`; const rootString = `root:[${node.data.root}]`; nodeString += rootString; if (node.data.metadata?.technologies && !optimizations?.skipTechnologies) { const technologiesString = `technologies:[${node.data.metadata.technologies.join( ',', )}]`; nodeString += technologiesString; } if (node.data.metadata?.owners && !optimizations?.skipOwners) { const ownersString = `owners:[${Object.keys( node.data.metadata.owners, ).join(',')}]`; nodeString += ownersString; } if (node.data.tags?.length && !optimizations?.skipTags) { const tagsString = `tags:[${node.data.tags.join(',')}]`; nodeString += tagsString; } nodeString += `</>\n`; serializedGraph += nodeString; }); return serializedGraph; } export function getProjectGraphErrorsPrompt( errors: NxError[], isPartial: boolean, ): string { return ` There were errors while calculating the project graph. ${ isPartial ? 'The following is the list of errors. If the user needs help, you can help fix the errors. Otherwise simply answer the question based on the information available.' : 'Due to these errors, project graph creation failed completely. You can help the user fix the errors or simply answer the question based on other information.' } ${errors.map((error) => `- ${getMessageForError(error)}`).join('\n')} `.trim(); }

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/nrwl/nx-console'

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