Skip to main content
Glama

deploy_pages

Configure GitHub Pages deployment workflow for static site generators with deployment tracking and preference learning capabilities.

Instructions

Set up GitHub Pages deployment workflow with deployment tracking and preference learning

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
repositoryYesRepository path or URL
ssgYes
branchNogh-pages
customDomainNo
projectPathNoLocal path to the project for tracking
projectNameNoProject name for tracking
analysisIdNoID from repository analysis for linking
userIdNoUser ID for preference trackingdefault

Implementation Reference

  • Core handler function that executes the deploy_pages tool: validates input, auto-detects SSG if needed, generates tailored GitHub Actions workflow, sets up deployment artifacts, and integrates with knowledge graph memory.
    export async function deployPages(
      args: unknown,
      context?: any,
    ): Promise<{ content: any[] }> {
      const startTime = Date.now();
      const {
        repository,
        ssg: providedSSG,
        branch,
        customDomain,
        projectPath,
        projectName,
        analysisId,
        userId,
      } = inputSchema.parse(args);
    
      // Declare ssg outside try block so it's accessible in catch
      let ssg:
        | "jekyll"
        | "hugo"
        | "docusaurus"
        | "mkdocs"
        | "eleventy"
        | undefined = providedSSG;
    
      // Report initial progress
      if (context?.meta?.progressToken) {
        await context.meta.reportProgress?.({
          progress: 0,
          total: 100,
        });
      }
    
      await context?.info?.("πŸš€ Starting GitHub Pages deployment configuration...");
    
      try {
        // Determine repository path (local or remote)
        const repoPath = repository.startsWith("http") ? "." : repository;
        await context?.info?.(`πŸ“‚ Target repository: ${repository}`);
    
        if (context?.meta?.progressToken) {
          await context.meta.reportProgress?.({
            progress: 10,
            total: 100,
          });
        }
    
        // Retrieve SSG from knowledge graph if not provided
        ssg = providedSSG;
        if (!ssg && analysisId) {
          await context?.info?.(
            `πŸ” Retrieving SSG recommendation from analysis ${analysisId}...`,
          );
          const retrievedSSG = await getSSGFromKnowledgeGraph(analysisId);
          if (retrievedSSG) {
            ssg = retrievedSSG as
              | "jekyll"
              | "hugo"
              | "docusaurus"
              | "mkdocs"
              | "eleventy";
            await context?.info?.(`βœ… Found recommended SSG: ${ssg}`);
          }
        } else if (ssg) {
          await context?.info?.(`ℹ️ Using specified SSG: ${ssg}`);
        }
    
        if (!ssg) {
          const errorResponse: MCPToolResponse = {
            success: false,
            error: {
              code: "SSG_NOT_SPECIFIED",
              message:
                "SSG parameter is required. Either provide it directly or ensure analysisId points to a project with SSG recommendations.",
              resolution:
                "Run analyze_repository and recommend_ssg first, or specify the SSG parameter explicitly.",
            },
            metadata: {
              toolVersion: "1.0.0",
              executionTime: Date.now() - startTime,
              timestamp: new Date().toISOString(),
            },
          };
          return formatMCPResponse(errorResponse);
        }
    
        if (context?.meta?.progressToken) {
          await context.meta.reportProgress?.({
            progress: 25,
            total: 100,
          });
        }
    
        // Detect documentation folder
        await context?.info?.("πŸ“‘ Detecting documentation folder...");
        const docsFolder = await detectDocsFolder(repoPath);
        await context?.info?.(
          `πŸ“ Documentation folder: ${docsFolder || "root directory"}`,
        );
    
        if (context?.meta?.progressToken) {
          await context.meta.reportProgress?.({
            progress: 40,
            total: 100,
          });
        }
    
        // Detect build configuration
        await context?.info?.(`βš™οΈ Detecting build configuration for ${ssg}...`);
        const buildConfig = await detectBuildConfig(repoPath, ssg, docsFolder);
    
        if (context?.meta?.progressToken) {
          await context.meta.reportProgress?.({
            progress: 55,
            total: 100,
          });
        }
    
        // Create .github/workflows directory
        await context?.info?.("πŸ“‚ Creating GitHub Actions workflow directory...");
        const workflowsDir = path.join(repoPath, ".github", "workflows");
        await fs.mkdir(workflowsDir, { recursive: true });
    
        if (context?.meta?.progressToken) {
          await context.meta.reportProgress?.({
            progress: 70,
            total: 100,
          });
        }
    
        // Generate workflow based on SSG and build config
        await context?.info?.(`✍️ Generating ${ssg} deployment workflow...`);
        const workflow = generateWorkflow(ssg, branch, customDomain, buildConfig);
        const workflowPath = path.join(workflowsDir, "deploy-docs.yml");
        await fs.writeFile(workflowPath, workflow);
        await context?.info?.(
          `βœ… Workflow created: .github/workflows/deploy-docs.yml`,
        );
    
        if (context?.meta?.progressToken) {
          await context.meta.reportProgress?.({
            progress: 85,
            total: 100,
          });
        }
    
        // Create CNAME file if custom domain is specified
        let cnameCreated = false;
        if (customDomain) {
          await context?.info?.(
            `🌐 Creating CNAME file for custom domain: ${customDomain}...`,
          );
          const cnamePath = path.join(repoPath, "CNAME");
          await fs.writeFile(cnamePath, customDomain);
          cnameCreated = true;
          await context?.info?.("βœ… CNAME file created");
        }
    
        const deploymentResult = {
          repository,
          ssg,
          branch,
          customDomain,
          workflowPath: "deploy-docs.yml",
          cnameCreated,
          repoPath,
          detectedConfig: {
            docsFolder: docsFolder || "root",
            buildCommand: buildConfig.buildCommand,
            outputPath: buildConfig.outputPath,
            packageManager: buildConfig.packageManager || "npm",
            workingDirectory: buildConfig.workingDirectory,
          },
        };
    
        // Phase 2.3: Track deployment setup in knowledge graph
        await context?.info?.("πŸ’Ύ Tracking deployment in Knowledge Graph...");
        try {
          // Create or update project in knowledge graph
          if (projectPath || projectName) {
            const timestamp = new Date().toISOString();
            const project = await createOrUpdateProject({
              id:
                analysisId ||
                `deploy_${repository.replace(/[^a-zA-Z0-9]/g, "_")}_${Date.now()}`,
              timestamp,
              path: projectPath || repository,
              projectName: projectName || repository,
              structure: {
                totalFiles: 0, // Unknown at this point
                languages: {},
                hasTests: false,
                hasCI: true, // We just added CI
                hasDocs: true, // Setting up docs deployment
              },
            });
    
            // Track successful deployment setup
            await trackDeployment(project.id, ssg, true, {
              buildTime: Date.now() - startTime,
            });
    
            // Update user preferences with SSG usage
            const userPreferenceManager = await getUserPreferenceManager(userId);
            await userPreferenceManager.trackSSGUsage({
              ssg,
              success: true, // Setup successful
              timestamp,
              projectType: projectPath || repository,
            });
          }
        } catch (trackingError) {
          // Don't fail the whole deployment if tracking fails
          console.warn(
            "Failed to track deployment in knowledge graph:",
            trackingError,
          );
        }
    
        if (context?.meta?.progressToken) {
          await context.meta.reportProgress?.({
            progress: 100,
            total: 100,
          });
        }
    
        const executionTime = Date.now() - startTime;
        await context?.info?.(
          `βœ… Deployment configuration complete! ${ssg} workflow created in ${Math.round(
            executionTime / 1000,
          )}s`,
        );
    
        const response: MCPToolResponse<typeof deploymentResult> = {
          success: true,
          data: deploymentResult,
          metadata: {
            toolVersion: "2.0.0",
            executionTime,
            timestamp: new Date().toISOString(),
          },
          recommendations: [
            {
              type: "info",
              title: "Deployment Workflow Created",
              description: `GitHub Actions workflow configured for ${ssg} deployment to ${branch} branch`,
            },
            ...(!providedSSG && analysisId
              ? [
                  {
                    type: "info" as const,
                    title: "SSG Auto-Detected",
                    description: `Retrieved ${ssg} from knowledge graph using analysisId`,
                  },
                ]
              : []),
            ...(docsFolder
              ? [
                  {
                    type: "info" as const,
                    title: "Documentation Folder Detected",
                    description: `Found documentation in '${docsFolder}/' folder. Workflow configured with working-directory.`,
                  },
                ]
              : []),
            ...(buildConfig.packageManager !== "npm"
              ? [
                  {
                    type: "info" as const,
                    title: "Package Manager Detected",
                    description: `Using ${buildConfig.packageManager} based on lockfile detection`,
                  },
                ]
              : []),
            ...(customDomain
              ? [
                  {
                    type: "info" as const,
                    title: "Custom Domain Configured",
                    description: `CNAME file created for ${customDomain}`,
                  },
                ]
              : []),
          ],
          nextSteps: [
            {
              action: "Verify Deployment Setup",
              toolRequired: "verify_deployment",
              description: "Check that all deployment requirements are met",
              priority: "high",
            },
            {
              action: "Commit and Push",
              toolRequired: "git",
              description: "Commit workflow files and push to trigger deployment",
              priority: "high",
            },
          ],
        };
    
        return formatMCPResponse(response);
      } catch (error) {
        // Phase 2.3: Track failed deployment setup
        try {
          if ((projectPath || projectName) && ssg) {
            const timestamp = new Date().toISOString();
            const project = await createOrUpdateProject({
              id:
                analysisId ||
                `deploy_${repository.replace(/[^a-zA-Z0-9]/g, "_")}_${Date.now()}`,
              timestamp,
              path: projectPath || repository,
              projectName: projectName || repository,
              structure: {
                totalFiles: 0,
                languages: {},
                hasTests: false,
                hasCI: false,
                hasDocs: false,
              },
            });
    
            // Track failed deployment (only if ssg is known)
            await trackDeployment(project.id, ssg, false, {
              errorMessage: String(error),
            });
    
            // Update user preferences with failed SSG usage
            const userPreferenceManager = await getUserPreferenceManager(userId);
            await userPreferenceManager.trackSSGUsage({
              ssg,
              success: false,
              timestamp,
              projectType: projectPath || repository,
            });
          }
        } catch (trackingError) {
          console.warn("Failed to track deployment failure:", trackingError);
        }
    
        const errorResponse: MCPToolResponse = {
          success: false,
          error: {
            code: "DEPLOYMENT_SETUP_FAILED",
            message: `Failed to setup deployment: ${error}`,
            resolution:
              "Ensure repository path is accessible and GitHub Actions are enabled",
          },
          metadata: {
            toolVersion: "1.0.0",
            executionTime: Date.now() - startTime,
            timestamp: new Date().toISOString(),
          },
        };
        return formatMCPResponse(errorResponse);
      }
    }
  • Zod schema for input validation defining parameters: repository (required), ssg (optional, auto-detected via KG), branch, customDomain, projectPath, etc.
    const inputSchema = z.object({
      repository: z.string(),
      ssg: z
        .enum(["jekyll", "hugo", "docusaurus", "mkdocs", "eleventy"])
        .optional()
        .describe(
          "Static site generator to use. If not provided, will be retrieved from knowledge graph using analysisId",
        ),
      branch: z.string().optional().default("gh-pages"),
      customDomain: z.string().optional(),
      projectPath: z
        .string()
        .optional()
        .describe("Local path to the project for tracking"),
      projectName: z.string().optional().describe("Project name for tracking"),
      analysisId: z
        .string()
        .optional()
        .describe("ID from repository analysis for linking and SSG retrieval"),
      userId: z
        .string()
        .optional()
        .default("default")
        .describe("User ID for preference tracking"),
    });
  • Helper to detect build configuration: parses package.json scripts/engines/lockfiles to determine buildCommand, outputPath, packageManager, etc. for the SSG.
    async function detectBuildConfig(
      repoPath: string,
      ssg: string,
      docsFolder: string | null,
    ): Promise<BuildConfig> {
      const workingDir = docsFolder || ".";
      const packageJsonPath = path.join(repoPath, workingDir, "package.json");
    
      const defaults: Record<string, BuildConfig> = {
        docusaurus: {
          workingDirectory: docsFolder,
          buildCommand: "npm run build",
          outputPath: "./build",
        },
        eleventy: {
          workingDirectory: docsFolder,
          buildCommand: "npm run build",
          outputPath: "./_site",
        },
        hugo: {
          workingDirectory: docsFolder,
          buildCommand: "hugo --minify",
          outputPath: "./public",
        },
        jekyll: {
          workingDirectory: docsFolder,
          buildCommand: "bundle exec jekyll build",
          outputPath: "./_site",
        },
        mkdocs: {
          workingDirectory: docsFolder,
          buildCommand: "mkdocs build",
          outputPath: "./site",
        },
      };
    
      const config = defaults[ssg] || defaults.docusaurus;
    
      try {
        const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
    
        // Detect build command from scripts
        const scripts = packageJson.scripts || {};
        if (scripts.build) {
          config.buildCommand = "npm run build";
        } else if (scripts["docs:build"]) {
          config.buildCommand = "npm run docs:build";
        } else if (scripts.start && scripts.start.includes("docusaurus")) {
          config.buildCommand = "npm run build";
        }
    
        // Detect package manager
        const hasYarnLock = await fs
          .access(path.join(repoPath, workingDir, "yarn.lock"))
          .then(() => true)
          .catch(() => false);
        const hasPnpmLock = await fs
          .access(path.join(repoPath, workingDir, "pnpm-lock.yaml"))
          .then(() => true)
          .catch(() => false);
    
        if (hasYarnLock) {
          config.packageManager = "yarn";
          config.buildCommand = config.buildCommand.replace("npm", "yarn");
        } else if (hasPnpmLock) {
          config.packageManager = "pnpm";
          config.buildCommand = config.buildCommand.replace("npm", "pnpm");
        } else {
          config.packageManager = "npm";
        }
    
        // Detect Node version from engines field
        if (packageJson.engines?.node) {
          config.nodeVersion = packageJson.engines.node;
        }
      } catch (error) {
        // If package.json doesn't exist or can't be read, use defaults
        console.warn("Using default build configuration:", error);
      }
    
      return config;
    }
  • Helper generating the complete GitHub Actions workflow YAML tailored to the detected SSG, including setup steps, build, and deploy-pages action.
    function generateWorkflow(
      ssg: string,
      branch: string,
      _customDomain: string | undefined,
      buildConfig: BuildConfig,
    ): string {
      const workingDirPrefix = buildConfig.workingDirectory
        ? `      working-directory: ${buildConfig.workingDirectory}\n`
        : "";
    
      const nodeVersion = buildConfig.nodeVersion || "20";
      const packageManager = buildConfig.packageManager || "npm";
    
      // Helper to get install command
      const getInstallCmd = () => {
        if (packageManager === "yarn") return "yarn install --frozen-lockfile";
        if (packageManager === "pnpm") return "pnpm install --frozen-lockfile";
        return "npm ci";
      };
    
      // Helper to add working directory to steps
      // const _addWorkingDir = (step: string) => {
      //   if (!buildConfig.workingDirectory) return step;
      //   return step.replace(
      //     /^(\s+)run:/gm,
      //     `$1working-directory: ${buildConfig.workingDirectory}\n$1run:`,
      //   );
      // };
    
      const workflows: Record<string, string> = {
        docusaurus: `name: Deploy Docusaurus to GitHub Pages
    
    on:
      push:
        branches: [main]
      workflow_dispatch:
    
    permissions:
      contents: read
      pages: write
      id-token: write
    
    concurrency:
      group: "pages"
      cancel-in-progress: false
    
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    
          - name: Setup Node.js
            uses: actions/setup-node@v4
            with:
              node-version: '${nodeVersion}'
              cache: '${packageManager}'${
                buildConfig.workingDirectory
                  ? `\n          cache-dependency-path: ${buildConfig.workingDirectory}/package-lock.json`
                  : ""
              }
    
          - name: Install dependencies
    ${workingDirPrefix}        run: ${getInstallCmd()}
    
          - name: Build website
    ${workingDirPrefix}        run: ${buildConfig.buildCommand}
    
          - name: Upload artifact
            uses: actions/upload-pages-artifact@v2
            with:
              path: ${
                buildConfig.workingDirectory
                  ? `${buildConfig.workingDirectory}/${buildConfig.outputPath}`
                  : buildConfig.outputPath
              }
    
      deploy:
        environment:
          name: github-pages
          url: \${{ steps.deployment.outputs.page_url }}
        runs-on: ubuntu-latest
        needs: build
        steps:
          - name: Deploy to GitHub Pages
            id: deployment
            uses: actions/deploy-pages@v3`,
    
        mkdocs: `name: Deploy MkDocs to GitHub Pages
    
    on:
      push:
        branches: [main]
      workflow_dispatch:
    
    permissions:
      contents: write
    
    jobs:
      deploy:
        runs-on: ubuntu-latest${
          buildConfig.workingDirectory
            ? `\n    defaults:\n      run:\n        working-directory: ${buildConfig.workingDirectory}`
            : ""
        }
        steps:
          - uses: actions/checkout@v4
    
          - name: Setup Python
            uses: actions/setup-python@v4
            with:
              python-version: '3.x'
    
          - name: Install dependencies
            run: |
              pip install -r requirements.txt
    
          - name: Build and Deploy
            run: mkdocs gh-deploy --force --branch ${branch}`,
    
        hugo: `name: Deploy Hugo to GitHub Pages
    
    on:
      push:
        branches: [main]
      workflow_dispatch:
    
    permissions:
      contents: read
      pages: write
      id-token: write
    
    concurrency:
      group: "pages"
      cancel-in-progress: false
    
    jobs:
      build:
        runs-on: ubuntu-latest${
          buildConfig.workingDirectory
            ? `\n    defaults:\n      run:\n        working-directory: ${buildConfig.workingDirectory}`
            : ""
        }
        steps:
          - name: Checkout
            uses: actions/checkout@v4
            with:
              submodules: recursive
    
          - name: Setup Hugo
            uses: peaceiris/actions-hugo@v2
            with:
              hugo-version: 'latest'
              extended: true
    
          - name: Build
            run: ${buildConfig.buildCommand}
    
          - name: Upload artifact
            uses: actions/upload-pages-artifact@v2
            with:
              path: ${
                buildConfig.workingDirectory
                  ? `${buildConfig.workingDirectory}/${buildConfig.outputPath}`
                  : buildConfig.outputPath
              }
    
      deploy:
        environment:
          name: github-pages
          url: \${{ steps.deployment.outputs.page_url }}
        runs-on: ubuntu-latest
        needs: build
        steps:
          - name: Deploy to GitHub Pages
            id: deployment
            uses: actions/deploy-pages@v3`,
    
        jekyll: `name: Deploy Jekyll to GitHub Pages
    
    on:
      push:
        branches: [main]
      workflow_dispatch:
    
    permissions:
      contents: read
      pages: write
      id-token: write
    
    concurrency:
      group: "pages"
      cancel-in-progress: false
    
    jobs:
      build:
        runs-on: ubuntu-latest${
          buildConfig.workingDirectory
            ? `\n    defaults:\n      run:\n        working-directory: ${buildConfig.workingDirectory}`
            : ""
        }
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    
          - name: Setup Ruby
            uses: ruby/setup-ruby@v1
            with:
              ruby-version: '3.1'
              bundler-cache: true${
                buildConfig.workingDirectory
                  ? `\n          working-directory: ${buildConfig.workingDirectory}`
                  : ""
              }
    
          - name: Build with Jekyll
            run: ${buildConfig.buildCommand}
            env:
              JEKYLL_ENV: production
    
          - name: Upload artifact
            uses: actions/upload-pages-artifact@v2${
              buildConfig.workingDirectory
                ? `\n        with:\n          path: ${buildConfig.workingDirectory}/${buildConfig.outputPath}`
                : ""
            }
    
      deploy:
        environment:
          name: github-pages
          url: \${{ steps.deployment.outputs.page_url }}
        runs-on: ubuntu-latest
        needs: build
        steps:
          - name: Deploy to GitHub Pages
            id: deployment
            uses: actions/deploy-pages@v3`,
    
        eleventy: `name: Deploy Eleventy to GitHub Pages
    
    on:
      push:
        branches: [main]
      workflow_dispatch:
    
    permissions:
      contents: read
      pages: write
      id-token: write
    
    concurrency:
      group: "pages"
      cancel-in-progress: false
    
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    
          - name: Setup Node.js
            uses: actions/setup-node@v4
            with:
              node-version: '${nodeVersion}'
              cache: '${packageManager}'${
                buildConfig.workingDirectory
                  ? `\n          cache-dependency-path: ${buildConfig.workingDirectory}/package-lock.json`
                  : ""
              }
    
          - name: Install dependencies
    ${workingDirPrefix}        run: ${getInstallCmd()}
    
          - name: Build site
    ${workingDirPrefix}        run: ${buildConfig.buildCommand}
    
          - name: Upload artifact
            uses: actions/upload-pages-artifact@v2
            with:
              path: ${
                buildConfig.workingDirectory
                  ? `${buildConfig.workingDirectory}/${buildConfig.outputPath}`
                  : buildConfig.outputPath
              }
    
      deploy:
        environment:
          name: github-pages
          url: \${{ steps.deployment.outputs.page_url }}
        runs-on: ubuntu-latest
        needs: build
        steps:
          - name: Deploy to GitHub Pages
            id: deployment
            uses: actions/deploy-pages@v3`,
      };
    
      return workflows[ssg] || workflows.jekyll;
    }
  • Tool referenced/registered in documentation workflow definitions as a key deployment step.
          tool: "deploy_pages",
          description: "Set up GitHub Pages deployment workflow",
          requiredInputs: ["repository", "ssg"],
          outputs: ["deploymentWorkflow", "deploymentInstructions"],
          optional: true,
        },
      ],
    },

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/tosin2013/documcp'

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