Skip to main content
Glama

contentrain_submit

Push contentrain/* branches to remote for Contentrain MCP platform processing. This tool handles branch submission while PR creation is managed by the platform.

Instructions

Push contentrain/* branches to remote. MCP is push-only — PR creation is handled by the platform. Do NOT manually push or create PRs.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
branchesNoSpecific branch names to push (omit for all contentrain/* branches)
messageNoOptional message for the push operation

Implementation Reference

  • The implementation of the contentrain_submit tool, which pushes local contentrain/* git branches to a configured remote.
    server.tool(
      'contentrain_submit',
      'Push contentrain/* branches to remote. MCP is push-only — PR creation is handled by the platform. Do NOT manually push or create PRs.',
      {
        branches: z.array(z.string()).optional().describe('Specific branch names to push (omit for all contentrain/* branches)'),
        message: z.string().optional().describe('Optional message for the push operation'),
      },
      async (input) => {
        const config = await readConfig(projectRoot)
        if (!config) {
          return {
            content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],
            isError: true,
          }
        }
    
        const git = simpleGit(projectRoot)
        const remoteName = process.env['CONTENTRAIN_REMOTE'] ?? 'origin'
    
        // Check remote exists
        let hasRemote = false
        try {
          const remotes = await git.getRemotes()
          hasRemote = remotes.some(r => r.name === remoteName)
        } catch {
          hasRemote = false
        }
    
        if (!hasRemote) {
          return {
            content: [{ type: 'text' as const, text: JSON.stringify({
              error: `No remote "${remoteName}" found. Configure a git remote first.`,
              next_steps: [`git remote add ${remoteName} <url>`],
            }) }],
            isError: true,
          }
        }
    
        try {
          // Determine the base branch for merge-status checks
          const baseBranch = config.repository?.default_branch
            ?? process.env['CONTENTRAIN_BRANCH']
            ?? ((await git.raw(['branch', '--show-current'])).trim() || 'main')
    
          // Get contentrain branches
          const branchSummary = await git.branchLocal()
    
          // Get branches NOT yet merged into base (only these need pushing)
          let unmergedRaw = ''
          try {
            unmergedRaw = await git.raw(['branch', '--no-merged', baseBranch])
          } catch {
            // If base branch doesn't exist yet, treat all as unmerged
            unmergedRaw = branchSummary.all.join('\n')
          }
          const unmergedSet = new Set(
            unmergedRaw.split('\n').map(b => b.replace(/^\*?\s+/, '').trim()).filter(Boolean),
          )
    
          let branchesToPush: string[]
    
          if (input.branches && input.branches.length > 0) {
            branchesToPush = input.branches.filter(b => branchSummary.all.includes(b))
            const missing = input.branches.filter(b => !branchSummary.all.includes(b))
            if (missing.length > 0) {
              return {
                content: [{ type: 'text' as const, text: JSON.stringify({
                  error: `Branches not found: ${missing.join(', ')}`,
                }) }],
                isError: true,
              }
            }
            // Filter out already-merged branches from explicit list
            branchesToPush = branchesToPush.filter(b => unmergedSet.has(b))
          } else {
            branchesToPush = branchSummary.all.filter(b => b.startsWith('contentrain/') && unmergedSet.has(b))
          }
    
          if (branchesToPush.length === 0) {
            return {
              content: [{ type: 'text' as const, text: JSON.stringify({
                error: 'No unmerged contentrain/* branches found to push.',
                next_steps: ['Make changes first with contentrain_content_save or contentrain_model_save'],
              }) }],
              isError: true,
            }
          }
    
          // Push each branch
          const pushed: string[] = []
          const errors: Array<{ branch: string; error: string }> = []
    
          for (const branch of branchesToPush) {
            try {
              await git.push(remoteName, branch)
              pushed.push(branch)
            } catch (error) {
              errors.push({
                branch,
                error: error instanceof Error ? error.message : String(error),
              })
            }
          }
    
          // Lazy cleanup: delete merged branches after push
          const cleanup = await cleanupMergedBranches(projectRoot)
    
          const nextSteps: string[] = []
          if (pushed.length > 0) nextSteps.push('Create PRs on your git platform for review')
          if (errors.length > 0) nextSteps.push('Fix push errors and retry')
          if (cleanup.remaining >= 50) nextSteps.push(`Warning: ${cleanup.remaining} active contentrain branches. Consider reviewing old branches.`)
    
          return {
            content: [{ type: 'text' as const, text: JSON.stringify({
              status: 'committed',
              message: `Pushed ${pushed.length} branch(es) to ${remoteName}.`,
              pushed,
              errors: errors.length > 0 ? errors : undefined,
              remote: remoteName,
              cleanup: cleanup.deleted > 0 ? { deleted: cleanup.deleted, remaining: cleanup.remaining } : undefined,
              next_steps: nextSteps,
            }, null, 2) }],
          }
        } catch (error) {
          return {
            content: [{ type: 'text' as const, text: JSON.stringify({
              error: `Submit failed: ${error instanceof Error ? error.message : String(error)}`,
            }) }],
            isError: true,
          }
        }
      },
    )
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It successfully establishes this is a write operation ('Push') and clarifies PR handling delegation, but lacks disclosure of failure modes (e.g., push conflicts), idempotency, or authentication requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences with zero waste: sentence 1 states purpose, sentence 2 defines scope limitations, sentence 3 provides operational guidance. Perfectly front-loaded and appropriately sized for the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the simple 2-parameter schema with full coverage and no output schema, the description adequately covers the tool's role. It appropriately omits return value details (no output schema exists), though it could benefit from mentioning conflict resolution behavior.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, establishing a baseline of 3. The description reinforces the 'contentrain/*' branch naming convention already present in the schema, adding conceptual context but no additional syntax or format details beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb ('Push') and resource ('contentrain/* branches'), clearly distinguishing this git operation from sibling content management tools like contentrain_content_save or contentrain_model_delete. The scope ('to remote') is explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Excellent guidance: explicitly states the tool is 'push-only', clarifies that 'PR creation is handled by the platform' (defining the boundary), and commands 'Do NOT manually push or create PRs', preventing incorrect manual workflows.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/Contentrain/ai'

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