We have 24 Plane tools. Now let's add intelligence: tools that analyze tickets, parse dependencies, and even operate Git and GitHub. This is where MCP becomes truly powerful - AI doesn't just call APIs, it understands your workflow.
By the end, you'll have 35 total tools including complexity analysis, Git operations, and GitHub PR management.
Intelligence Tools: Making AI Smarter
These 4 tools help AI understand project complexity:
- Complexity Analysis - Score tickets 0-100
- Should Split - Recommend breaking down large tasks
- Decompose - Generate sub-task suggestions
- Parse Dependencies - Extract dependency graphs
Why Intelligence Tools Matter
Without intelligence:
Claude: "I'll create this ticket for you."
With intelligence:
Claude: "This ticket scores 87/100 complexity. I recommend splitting it into 3 sub-tasks:
1. Database schema changes
2. API endpoint implementation
3. Frontend integration
Would you like me to create these sub-issues?"
Complexity Analysis Algorithm
Create src/utils/complexity.py:
"""
Ticket Complexity Analysis
Adapted from project-team autonomous agent
"""
import re
from typing import Dict, List
def analyze_ticket_complexity(ticket: Dict) -> Dict:
"""
Score ticket complexity 0-100 based on:
- Description length
- Number of technical keywords
- File/component mentions
- Estimated scope
"""
score = 0
factors = {}
description = ticket.get('description', '') or ''
# Factor 1: Description length (0-30 points)
length = len(description)
if length > 1000:
desc_score = 30
elif length > 500:
desc_score = 20
elif length > 200:
desc_score = 10
else:
desc_score = 5
score += desc_score
factors['description_length'] = desc_score
# Factor 2: Technical keywords (0-25 points)
keywords = [
r'\bapi\b', r'\bdatabase\b', r'\bschema\b', r'\bmigration\b',
r'\bauth\b', r'\bsecurity\b', r'\bperformance\b',
r'\brefactor\b', r'\barchitecture\b', r'\bintegration\b'
]
keyword_count = sum(1 for kw in keywords if re.search(kw, description, re.I))
keyword_score = min(keyword_count * 5, 25)
score += keyword_score
factors['technical_keywords'] = keyword_score
# Factor 3: File/component mentions (0-20 points)
file_patterns = [
r'\.py\b', r'\.js\b', r'\.ts\b', r'\.tsx\b',
r'\.css\b', r'\.html\b', r'\.sql\b'
]
file_count = sum(1 for pattern in file_patterns if re.search(pattern, description, re.I))
file_score = min(file_count * 5, 20)
score += file_score
factors['file_mentions'] = file_score
# Factor 4: Multiple components (0-25 points)
component_keywords = [
'frontend', 'backend', 'database', 'api',
'ui', 'ux', 'deployment', 'testing', 'docs'
]
component_count = sum(1 for comp in component_keywords if comp in description.lower())
component_score = min(component_count * 5, 25)
score += component_score
factors['components'] = component_score
return {
'score': min(score, 100),
'factors': factors,
'recommendation': _get_recommendation(score)
}
def _get_recommendation(score: int) -> str:
"""Get recommendation based on complexity score"""
if score >= 70:
return "High complexity - strongly recommend splitting into sub-tasks"
elif score >= 50:
return "Medium complexity - consider breaking down"
elif score >= 30:
return "Low-medium complexity - manageable as single task"
else:
return "Low complexity - can be completed as-is"
Dependency Parsing
Add to complexity.py:
def parse_ticket_dependencies(ticket: Dict) -> List[str]:
"""
Parse ticket description to find dependencies
Looks for patterns like:
- "depends on PROJ-123"
- "requires PROJ-456"
- "blocked by PROJ-789"
"""
dependencies = []
description = ticket.get('description', '')
patterns = [
r'depends\s+on\s+([A-Z]+-\d+)',
r'requires?\s+([A-Z]+-\d+)',
r'blocked\s+by\s+([A-Z]+-\d+)',
r'needs?\s+([A-Z]+-\d+)',
]
for pattern in patterns:
matches = re.findall(pattern, description, re.IGNORECASE)
dependencies.extend(matches)
# Remove duplicates
return list(set(dep.upper() for dep in dependencies))
def topological_sort(tickets: List[Dict]) -> List[Dict]:
"""
Sort tickets by dependencies using Kahn's algorithm
Dependencies come before dependents
"""
# Build identifier map
ticket_map = {}
for ticket in tickets:
identifier = f"{ticket['project']}-{ticket['sequence_id']}"
ticket_map[identifier] = ticket
# Build dependency graph
graph = {tid: [] for tid in ticket_map}
in_degree = {tid: 0 for tid in ticket_map}
for ticket_id, ticket in ticket_map.items():
deps = parse_ticket_dependencies(ticket)
for dep in deps:
if dep in ticket_map:
graph[dep].append(ticket_id)
in_degree[ticket_id] += 1
# Kahn's algorithm
queue = [tid for tid, degree in in_degree.items() if degree == 0]
sorted_ids = []
while queue:
queue.sort() # Maintain consistent ordering
current = queue.pop(0)
sorted_ids.append(current)
for neighbor in graph[current]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
# Return sorted tickets
return [ticket_map[tid] for tid in sorted_ids if tid in ticket_map]
Registering Intelligence Tools
Add to src/server.py:
from utils.complexity import (
analyze_ticket_complexity,
parse_ticket_dependencies,
topological_sort
)
@mcp.tool()
def plane_analyze_complexity(project_id: str, issue_id: str) -> str:
"""
Analyze ticket complexity and get recommendations.
Returns score 0-100 with breakdown of factors.
"""
try:
issue = plane_tools.get_issue({'project_id': project_id, 'issue_id': issue_id})
issue_data = json.loads(issue)
analysis = analyze_ticket_complexity(issue_data)
return json.dumps({
'issue_id': issue_id,
'complexity_score': analysis['score'],
'factors': analysis['factors'],
'recommendation': analysis['recommendation']
}, indent=2)
except Exception as e:
return f"Error analyzing complexity: {str(e)}"
@mcp.tool()
def plane_should_split(project_id: str, issue_id: str) -> str:
"""
Determine if ticket should be split into sub-tasks.
Returns boolean recommendation with reasoning.
"""
try:
issue = plane_tools.get_issue({'project_id': project_id, 'issue_id': issue_id})
issue_data = json.loads(issue)
analysis = analyze_ticket_complexity(issue_data)
should_split = analysis['score'] >= 70
return json.dumps({
'should_split': should_split,
'complexity_score': analysis['score'],
'reason': analysis['recommendation'],
'suggestion': 'Create 2-4 sub-tasks focusing on separate concerns' if should_split else 'Can be completed as single task'
}, indent=2)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def plane_parse_dependencies(project_id: str, issue_id: str) -> str:
"""
Parse ticket description to extract dependencies.
Returns list of ticket IDs this issue depends on.
"""
try:
issue = plane_tools.get_issue({'project_id': project_id, 'issue_id': issue_id})
issue_data = json.loads(issue)
dependencies = parse_ticket_dependencies(issue_data)
return json.dumps({
'issue_id': issue_id,
'dependencies': dependencies,
'count': len(dependencies),
'message': f"Found {len(dependencies)} dependencies" if dependencies else "No dependencies found"
}, indent=2)
except Exception as e:
return f"Error parsing dependencies: {str(e)}"
Git Integration (8 Tools)
Now let's add Git operations. Create src/tools/git_tools.py:
"""
Git MCP Tools
"""
import subprocess
import json
from typing import Dict, Any, Tuple
class GitTools:
"""Git operations via subprocess"""
def _git_command(self, repo_path: str, command: List[str]) -> Tuple[bool, str]:
"""Execute git command and return (success, output)"""
try:
result = subprocess.run(
['git'] + command,
cwd=repo_path,
capture_output=True,
text=True,
timeout=60
)
output = result.stdout + result.stderr
return result.returncode == 0, output.strip()
except subprocess.TimeoutExpired:
return False, "Git command timed out after 60 seconds"
except Exception as e:
return False, f"Error: {str(e)}"
def git_status(self, arguments: Dict[str, Any]) -> str:
"""Show working tree status"""
repo_path = arguments.get('repo_path', '.')
success, output = self._git_command(repo_path, ['status', '--short'])
return json.dumps({'success': success, 'output': output}, indent=2)
def git_diff(self, arguments: Dict[str, Any]) -> str:
"""Show changes in working directory"""
repo_path = arguments.get('repo_path', '.')
file_path = arguments.get('file_path')
cmd = ['diff']
if file_path:
cmd.append(file_path)
success, output = self._git_command(repo_path, cmd)
return json.dumps({'success': success, 'diff': output}, indent=2)
def git_commit(self, arguments: Dict[str, Any]) -> str:
"""Create commit with message"""
repo_path = arguments.get('repo_path', '.')
message = arguments.get('message')
success, output = self._git_command(repo_path, ['commit', '-m', message])
return json.dumps({
'success': success,
'message': message if success else 'Commit failed',
'output': output
}, indent=2)
# ... Add: git_push, git_pull, git_checkout, git_log, git_current_branch
# See GitHub for complete implementation
Register Git Tools
from tools.git_tools import GitTools
git_tools = GitTools()
@mcp.tool()
def git_status(repo_path: str = ".") -> str:
"""Show git working tree status"""
return git_tools.git_status({'repo_path': repo_path})
@mcp.tool()
def git_commit(message: str, repo_path: str = ".") -> str:
"""Create git commit with message"""
return git_tools.git_commit({'message': message, 'repo_path': repo_path})
@mcp.tool()
def git_diff(file_path: str = None, repo_path: str = ".") -> str:
"""Show git diff for file or entire repo"""
return git_tools.git_diff({'file_path': file_path, 'repo_path': repo_path})
# ... Register remaining 5 Git tools
GitHub Integration (3 Tools)
Create src/tools/github_tools.py:
"""
GitHub MCP Tools
"""
import os
import requests
import json
from typing import Dict, Any
class GitHubTools:
"""GitHub API operations"""
def __init__(self):
self.token = os.getenv('GITHUB_TOKEN')
self.headers = {
'Authorization': f'token {self.token}',
'Accept': 'application/vnd.github.v3+json'
}
def create_pr(self, arguments: Dict[str, Any]) -> str:
"""Create pull request"""
owner = arguments.get('owner')
repo = arguments.get('repo')
title = arguments.get('title')
head = arguments.get('head') # branch name
base = arguments.get('base', 'main')
body = arguments.get('body', '')
url = f'https://api.github.com/repos/{owner}/{repo}/pulls'
data = {
'title': title,
'head': head,
'base': base,
'body': body
}
try:
response = requests.post(url, headers=self.headers, json=data)
response.raise_for_status()
pr = response.json()
return json.dumps({
'success': True,
'pr_number': pr['number'],
'url': pr['html_url'],
'message': f"Created PR #{pr['number']}: {title}"
}, indent=2)
except Exception as e:
return f"Error creating PR: {str(e)}"
# ... Add: get_pr, list_prs
Register GitHub Tools
from tools.github_tools import GitHubTools
github_tools = GitHubTools()
@mcp.tool()
def github_create_pr(
owner: str,
repo: str,
title: str,
head: str,
base: str = "main",
body: str = ""
) -> str:
"""
Create GitHub pull request.
Args:
owner: Repository owner
repo: Repository name
title: PR title
head: Branch to merge from
base: Branch to merge into (default: main)
body: PR description (markdown)
"""
return github_tools.create_pr({
'owner': owner,
'repo': repo,
'title': title,
'head': head,
'base': base,
'body': body
})
Complete Tool Count
We now have 35 total tools:
- Plane Tools: 24 (from Part 2)
- Intelligence Tools: 4 (new)
- Git Tools: 8 (new)
- GitHub Tools: 3 (new)
Real-World Workflow Example
Now Claude can handle complete project workflows:
User: "Create a feature branch for the new auth system, analyze complexity of PORTF-45, and if it's complex, split it into sub-tasks"
Claude's actions:
git_current_branch()- Check current branchgit_checkout(branch="feature/auth-system", create=True)- Create branchplane_analyze_complexity(project_id="PORTF", issue_id="45")- Analyze- If score > 70:
plane_should_split(...)- Confirm recommendationplane_create_sub_issue(...)× 3 - Create sub-tasksplane_parse_dependencies(...)- Check dependencies
- Respond with summary and next steps
This is autonomous project management powered by MCP!
Code Reuse Pattern
Notice we adapted complexity.py from an existing autonomous agent system (project-team). This is a key pattern:
✅ Do:
- Extract proven algorithms from existing projects
- Generalize them for MCP tools
- Maintain error handling and edge cases
- Keep business logic separate from MCP registration
Example structure:
src/
├── utils/ # Pure business logic (reusable)
│ ├── complexity.py
│ └── parsers.py
├── tools/ # MCP tool wrappers
│ ├── plane_tools.py
│ ├── git_tools.py
│ └── github_tools.py
└── server.py # MCP registration only
This separation means:
- Business logic can be unit tested
- Tools can be reused in other projects
- MCP layer stays thin and focused
What We've Built
✅ 4 intelligence tools for ticket analysis ✅ 8 Git tools for version control ✅ 3 GitHub tools for PR management ✅ 35 total tools covering complete project workflow ✅ Code reuse patterns from autonomous agents
Try this: Ask Claude to analyze all your backlog tickets and sort them by dependency order!
Next: Docker & Traefik
In Part 4, we'll deploy this to production:
- Dual-mode transport (stdio + SSE)
- Docker containerization
- Traefik reverse proxy with SSL
- Environment-based configuration
- Health checks and monitoring
→ Continue to Part 4: Docker & Traefik Deployment