.claude/plugins/jira-orchestrator/skills/harness-mcp/SKILL.md
Harness MCP (Model Context Protocol) server integration for AI-powered CD operations, pipeline management, Git repositories, pull requests, code review comments, and bidirectional Jira synchronization
npx skillsauth add markus41/demo-0.2 harness-mcpInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive Harness MCP (Model Context Protocol) server integration for AI-powered CD operations, Git repository management, and pull request workflows with the Jira Orchestrator.
The Harness MCP Server enables AI agents to interact with Harness tools using a unified protocol, providing endpoints for:
# Required Harness Configuration
export HARNESS_API_KEY="your-harness-api-key"
export HARNESS_DEFAULT_ORG_ID="your-org-id"
export HARNESS_DEFAULT_PROJECT_ID="your-project-id"
export HARNESS_BASE_URL="https://app.harness.io" # Optional, defaults to this
export HARNESS_ACCOUNT_ID="your-account-id"
Add Harness MCP to your Claude Code configuration:
{
"mcpServers": {
"harness": {
"command": "npx",
"args": [
"-y",
"@anthropic-ai/mcp-harness"
],
"env": {
"HARNESS_API_KEY": "${HARNESS_API_KEY}",
"HARNESS_DEFAULT_ORG_ID": "${HARNESS_DEFAULT_ORG_ID}",
"HARNESS_DEFAULT_PROJECT_ID": "${HARNESS_DEFAULT_PROJECT_ID}",
"HARNESS_BASE_URL": "${HARNESS_BASE_URL}"
}
}
}
}
docker run -e HARNESS_API_KEY=$HARNESS_API_KEY \
-e HARNESS_DEFAULT_ORG_ID=$HARNESS_DEFAULT_ORG_ID \
-e HARNESS_DEFAULT_PROJECT_ID=$HARNESS_DEFAULT_PROJECT_ID \
harness/mcp-server:latest
{
"mcp.servers": {
"harness": {
"command": "npx",
"args": ["-y", "@harness/mcp-server"],
"env": {
"HARNESS_API_KEY": "${env:HARNESS_API_KEY}",
"HARNESS_DEFAULT_ORG_ID": "${env:HARNESS_DEFAULT_ORG_ID}",
"HARNESS_DEFAULT_PROJECT_ID": "${env:HARNESS_DEFAULT_PROJECT_ID}"
}
}
}
}
| Tool | Description |
|------|-------------|
| harness_get_connector | Get details of a specific connector |
| harness_list_connectors | List all connectors with filtering |
| harness_get_connector_catalogue | Get available connector types |
| Tool | Description |
|------|-------------|
| harness_list_pipelines | List pipelines in project |
| harness_get_pipeline | Get pipeline details and YAML |
| harness_trigger_pipeline | Trigger pipeline execution |
| Tool | Description |
|------|-------------|
| harness_get_execution | Get execution details |
| harness_list_executions | List recent executions |
| harness_get_execution_url | Get execution dashboard URL |
| Tool | Description |
|------|-------------|
| harness_list_dashboards | List all dashboards |
| harness_get_dashboard | Get specific dashboard data |
| Tool | Description |
|------|-------------|
| harness_get_repository | Get details of a specific repository |
| harness_list_repositories | List all repositories in project |
| Tool | Description |
|------|-------------|
| harness_get_pull_request | Get details of a specific pull request |
| harness_list_pull_requests | List pull requests in a repository |
| harness_create_pull_request | Create a new pull request |
| harness_get_pull_request_checks | Get status checks for a PR |
| harness_get_pull_request_activities | Get comments and activities for a PR |
# List all repositories
repos = harness_list_repositories(
org_id="${HARNESS_ORG_ID}",
project_id="${HARNESS_PROJECT_ID}"
)
# Get specific repository details
repo = harness_get_repository(
repo_id="my-application",
org_id="${HARNESS_ORG_ID}",
project_id="${HARNESS_PROJECT_ID}"
)
# Create a PR linked to a Jira issue
pr = harness_create_pull_request(
repo_id="my-application",
title="PROJ-123: Implement user authentication",
source_branch="feature/PROJ-123-user-auth",
target_branch="main",
description="""
## Summary
Implements user authentication feature.
## Jira Issue
[PROJ-123](https://company.atlassian.net/browse/PROJ-123)
## Changes
- Added login/logout endpoints
- Implemented JWT token handling
- Added authentication middleware
- Unit tests included
## Testing
- [x] Unit tests passing
- [x] Integration tests passing
- [ ] Manual testing required
"""
)
print(f"PR created: {pr.url}")
# Get PR details
pr = harness_get_pull_request(
repo_id="my-application",
pr_number=42
)
print(f"PR Status: {pr.state}")
print(f"Mergeable: {pr.mergeable}")
print(f"Conflicts: {pr.has_conflicts}")
# Get status checks (pipeline results)
checks = harness_get_pull_request_checks(
repo_id="my-application",
pr_number=42
)
for check in checks:
print(f" {check.name}: {check.status}")
# Get all activities (comments, reviews, status changes)
activities = harness_get_pull_request_activities(
repo_id="my-application",
pr_number=42
)
for activity in activities:
if activity.type == "comment":
print(f"Comment by {activity.author}:")
print(f" File: {activity.file_path}:{activity.line_number}")
print(f" Body: {activity.body}")
elif activity.type == "review":
print(f"Review by {activity.author}: {activity.state}")
# States: APPROVED, CHANGES_REQUESTED, COMMENTED
elif activity.type == "status_change":
print(f"Status changed to: {activity.new_status}")
# Sync PR comments to Jira
activities = harness_get_pull_request_activities(
repo_id="my-application",
pr_number=42
)
# Build summary for Jira comment
review_summary = []
for activity in activities:
if activity.type == "review":
review_summary.append(
f"- **{activity.author}**: {activity.state}"
)
elif activity.type == "comment":
review_summary.append(
f"- Comment on `{activity.file_path}`: {activity.body[:100]}..."
)
# Add summary to Jira
jira_add_comment(
issue_key="PROJ-123",
body=f"""
## PR Review Update
**PR:** [#42 - Implement user auth]({pr.url})
**Status:** {pr.state}
### Reviews & Comments
{chr(10).join(review_summary)}
"""
)
Configure automatic status transitions based on PR events:
# .jira/pr-sync.yml
pr_sync:
enabled: true
# Extract Jira keys from PR metadata
jira_key_patterns:
- title: "^([A-Z]+-\\d+)" # PROJ-123: Title
- branch: "feature/([A-Z]+-\\d+)" # feature/PROJ-123
- branch: "bugfix/([A-Z]+-\\d+)" # bugfix/PROJ-456
# Map PR events to Jira transitions
transitions:
pr_created:
transition: "In Review"
comment: "Pull request created: {pr_url}"
pr_approved:
transition: "Approved"
comment: "PR approved by {approver}"
pr_changes_requested:
transition: "In Progress"
comment: "Changes requested by {reviewer}"
pr_merged:
transition: "Done"
comment: "PR merged to {target_branch}"
pr_closed:
transition: "Cancelled"
comment: "PR closed without merging"
# Fields to update
fields:
pr_url: "customfield_10200"
pr_status: "customfield_10201"
reviewers: "customfield_10202"
# When a reviewer adds comments, sync to Jira
def sync_review_comments_to_jira(repo_id, pr_number, jira_key):
activities = harness_get_pull_request_activities(
repo_id=repo_id,
pr_number=pr_number
)
# Group comments by file
comments_by_file = {}
for activity in activities:
if activity.type == "comment":
file = activity.file_path
if file not in comments_by_file:
comments_by_file[file] = []
comments_by_file[file].append({
"line": activity.line_number,
"author": activity.author,
"body": activity.body,
"resolved": activity.resolved
})
# Format for Jira
jira_body = "## Code Review Comments\n\n"
for file, comments in comments_by_file.items():
jira_body += f"### `{file}`\n"
for c in comments:
status = "✅" if c["resolved"] else "💬"
jira_body += f"- {status} Line {c['line']} ({c['author']}): {c['body']}\n"
jira_add_comment(issue_key=jira_key, body=jira_body)
connector:
name: jira-connector
identifier: jira_connector
orgIdentifier: default
projectIdentifier: your_project
type: Jira
spec:
jiraUrl: https://your-company.atlassian.net
spec:
jiraUrl: https://your-company.atlassian.net
auth:
type: UsernamePassword
spec:
username: [email protected]
passwordRef: jira_api_token # Harness secret reference
Required Scopes:
read:jira-userread:jira-workwrite:jira-workspec:
jiraUrl: https://jira.internal.company.com
delegateSelectors:
- delegate-name
auth:
type: PersonalAccessToken
spec:
patRef: jira_pat_secret # Harness secret reference
Note: Requires Harness Delegate version 78707+
spec:
jiraUrl: https://api.atlassian.com/ex/jira/{cloud_id}
auth:
type: OAuth
spec:
clientId: your_oauth_client_id
clientSecretRef: oauth_secret
tokenEndpoint: https://auth.atlassian.com/oauth/token
Select Harness Delegates that have network access to your Jira instance:
spec:
delegateSelectors:
- primary-delegate
- backup-delegate
Click Save and Continue - Harness automatically tests the connection.
- step:
name: Create Jira Issue
identifier: createJiraIssue
type: JiraCreate
timeout: 5m
spec:
connectorRef: jira_connector
projectKey: PROJ
issueType: Task
fields:
- name: Summary
value: "Deployment: <+pipeline.name> - <+pipeline.sequenceId>"
- name: Description
value: |
Deployment triggered by: <+pipeline.triggeredBy.name>
Environment: <+env.name>
Service: <+service.name>
Artifact: <+artifact.image>
- name: Priority
value: Medium
- name: Labels
value: ["deployment", "automation", "<+env.name>"]
- step:
name: Update Jira Issue
identifier: updateJiraIssue
type: JiraUpdate
timeout: 5m
spec:
connectorRef: jira_connector
issueKey: <+pipeline.variables.jiraIssueKey>
fields:
- name: Status
value: Done
- name: customfield_10100
value: <+artifact.tag>
transitionTo:
transitionName: Done
status: Done
- step:
name: Jira Approval
identifier: jiraApproval
type: JiraApproval
timeout: 1d
spec:
connectorRef: jira_connector
projectKey: PROJ
issueKey: <+pipeline.variables.jiraIssueKey>
approvalCriteria:
type: KeyValues
spec:
matchAnyCondition: true
conditions:
- key: Status
operator: equals
value: Approved
rejectionCriteria:
type: KeyValues
spec:
matchAnyCondition: true
conditions:
- key: Status
operator: equals
value: Rejected
The harness-jira-sync agent automatically:
Add to .jira/config.yml:
harness:
account:
account_id: "${HARNESS_ACCOUNT_ID}"
org_id: "${HARNESS_ORG_ID}"
project_id: "${HARNESS_PROJECT_ID}"
api:
base_url: "https://app.harness.io"
api_key: "${HARNESS_API_KEY}"
# MCP Integration
mcp:
enabled: true
tools:
- harness_get_connector
- harness_list_pipelines
- harness_get_execution
- harness_list_executions
# Jira Connector Reference
jira_connector_ref: "jira_connector"
# Sync Configuration
sync:
auto_create_issues: true
auto_transition: true
environments:
dev: "In Development"
staging: "In QA"
prod: "Released"
# Get Jira connector details
connector = harness_get_connector(
connector_id="jira_connector",
org_id="default",
project_id="my_project"
)
# List recent pipeline executions
executions = harness_list_executions(
pipeline_id="deploy_pipeline",
limit=10
)
# Get specific execution details
execution = harness_get_execution(
execution_id="abc123",
org_id="default",
project_id="my_project"
)
| Issue | Solution | |-------|----------| | API Key invalid | Regenerate token in Harness UI | | Network timeout | Check delegate connectivity | | Permission denied | Verify API key permissions | | Jira unreachable | Check firewall/proxy settings |
Error: INVALID_CREDENTIAL
Solution: Verify HARNESS_API_KEY is correct and not expired
Error: DELEGATE_NOT_AVAILABLE
Solution: Ensure delegate is running and selected in connector
Error: JIRA_AUTHENTICATION_FAILED
Solution: Verify Jira credentials (email + API token or PAT)
Enable verbose logging:
export HARNESS_LOG_LEVEL=debug
export MCP_DEBUG=true
The Jira orchestrator supports VS Code workspaces with multiple repositories, enabling coordinated operations across all repos linked to a Jira issue.
The system automatically detects repositories in:
.code-workspace)Create .jira/harness-workspace.yml in your workspace root:
harness:
workspace:
# Repositories in this workspace
repositories:
- identifier: frontend-app
path: ./frontend
jira_project: FRONT
description: "React frontend application"
- identifier: backend-api
path: ./backend
jira_project: BACK
description: "Node.js API backend"
- identifier: shared-libs
path: ./libs
jira_project: SHARED
description: "Shared TypeScript libraries"
# Auto-create repos if they don't exist
auto_create_repos: true
# Default branch for all repos
default_branch: main
# PR review settings
review:
# Review all PRs across workspace
cross_repo_review: true
# Auto-approve thresholds
auto_approve:
enabled: false
max_files: 5
excluded_patterns:
- "*.md"
- "*.txt"
# Jira integration
jira:
sync_enabled: true
aggregate_prs: true # Show all PRs in single Jira comment
from lib.harness_code_api import HarnessCodeAPI
client = HarnessCodeAPI()
# Setup workspace repos (creates if missing)
repos = client.setup_workspace_repos([
{"identifier": "frontend", "description": "React frontend", "path": "./frontend"},
{"identifier": "backend", "description": "API backend", "path": "./backend"},
{"identifier": "shared", "description": "Shared libs", "path": "./libs"}
])
# Get all PRs across workspace for a Jira issue
prs = client.get_workspace_prs(
repo_identifiers=["frontend", "backend", "shared"],
state="open",
jira_key="PROJ-123"
)
# Review all PRs in workspace
results = client.review_workspace_prs(
repo_identifiers=["frontend", "backend", "shared"],
jira_key="PROJ-123",
auto_approve=False
)
source lib/harness-code-api.sh
# Detect repos in VS Code workspace
harness_detect_workspace_repos
# Show status of all workspace repos
harness_workspace_status
# Create PRs for all changed repos
harness_workspace_create_prs "PROJ-123" "main"
Create repositories programmatically via the Harness Code REST API.
from lib.harness_code_api import HarnessCodeAPI
client = HarnessCodeAPI()
# Create a new repository
repo = client.create_repository(
identifier="my-new-service",
description="Microservice for user management",
default_branch="main",
is_public=False,
readme=True,
license="MIT",
gitignore="Node"
)
print(f"Created: {repo['identifier']}")
source lib/harness-code-api.sh
# Create repository
harness_create_repo "my-service" "User management service" "main" "false"
The ensure_repository_exists method checks if a repo exists and creates it if not:
# Will get existing or create new
repo = client.ensure_repository_exists(
identifier="my-service",
description="My service description"
)
| Operation | Method | Endpoint |
|-----------|--------|----------|
| List Repos | GET | /v1/repos or /v1/spaces/{space}/repos |
| Get Repo | GET | /v1/repos/{repo} |
| Create Repo | POST | /v1/repos |
| Update Repo | PATCH | /v1/repos/{repo} |
| Delete Repo | DELETE | /v1/repos/{repo} |
{
"identifier": "my-repo",
"description": "Repository description",
"default_branch": "main",
"is_public": false,
"parent_ref": "my-space",
"readme": true,
"license": "MIT",
"gitignore": "Node"
}
The Harness MCP Server provides read operations for PR activities, but for write operations (creating comments, submitting reviews, merging), use the Harness Code REST API directly.
# Harness Code API (Gitness-based)
HARNESS_CODE_API="${HARNESS_BASE_URL}/code/api/v1"
# For Harness SaaS
HARNESS_CODE_API="https://app.harness.io/code/api/v1"
# All requests require the x-api-key header
curl -H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
"${HARNESS_CODE_API}/repos/{repo-ref}/pullreq/{pr-number}/comments"
| Operation | Method | Endpoint |
|-----------|--------|----------|
| Create Comment | POST | /v1/repos/{repo-ref}/pullreq/{pr-number}/comments |
| Update Comment | PATCH | /v1/repos/{repo-ref}/pullreq/{pr-number}/comments/{comment-id} |
| Delete Comment | DELETE | /v1/repos/{repo-ref}/pullreq/{pr-number}/comments/{comment-id} |
| Apply Suggestions | POST | /v1/repos/{repo-ref}/pullreq/{pr-number}/comments/apply-suggestions |
| Submit Review | POST | /v1/repos/{repo-ref}/pullreq/{pr-number}/reviews |
| Add Reviewer | POST | /v1/repos/{repo-ref}/pullreq/{pr-number}/reviewers |
| Merge PR | POST | /v1/repos/{repo-ref}/pullreq/{pr-number}/merge |
# Add a general comment to a PR
curl -X POST "${HARNESS_CODE_API}/repos/${REPO_REF}/pullreq/${PR_NUMBER}/comments" \
-H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"text": "Great work on this implementation! A few suggestions below."
}'
# Add a comment on specific lines of code
curl -X POST "${HARNESS_CODE_API}/repos/${REPO_REF}/pullreq/${PR_NUMBER}/comments" \
-H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"text": "Consider using a constant here for better maintainability.",
"path": "src/services/auth.ts",
"line_start": 42,
"line_end": 45,
"line_start_new": true,
"line_end_new": true,
"source_commit_sha": "abc123...",
"target_commit_sha": "def456..."
}'
# Reply to an existing comment thread
curl -X POST "${HARNESS_CODE_API}/repos/${REPO_REF}/pullreq/${PR_NUMBER}/comments" \
-H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"text": "Good point, I will update this.",
"parent_id": 12345
}'
{
"text": "string (required) - The comment content (Markdown supported)",
"parent_id": "number (optional) - Parent comment ID for replies",
"path": "string (optional) - File path for code comments",
"line_start": "number (optional) - Starting line number",
"line_end": "number (optional) - Ending line number",
"line_start_new": "boolean - true=new file side, false=old file side",
"line_end_new": "boolean - true=new file side, false=old file side",
"source_commit_sha": "string (optional) - Source commit for code comments",
"target_commit_sha": "string (optional) - Target commit for code comments"
}
# Approve the pull request
curl -X POST "${HARNESS_CODE_API}/repos/${REPO_REF}/pullreq/${PR_NUMBER}/reviews" \
-H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"commit_sha": "abc123def456...",
"decision": "approved"
}'
# Request changes on the pull request
curl -X POST "${HARNESS_CODE_API}/repos/${REPO_REF}/pullreq/${PR_NUMBER}/reviews" \
-H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"commit_sha": "abc123def456...",
"decision": "changereq"
}'
# Submit a review without approval decision
curl -X POST "${HARNESS_CODE_API}/repos/${REPO_REF}/pullreq/${PR_NUMBER}/reviews" \
-H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"commit_sha": "abc123def456...",
"decision": "reviewed"
}'
| Decision | Description |
|----------|-------------|
| approved | Approve the PR for merge |
| changereq | Request changes before merge |
| reviewed | Mark as reviewed without approval/rejection |
# Add a reviewer to the PR
curl -X POST "${HARNESS_CODE_API}/repos/${REPO_REF}/pullreq/${PR_NUMBER}/reviewers" \
-H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"reviewer_id": 12345
}'
# Merge the pull request
curl -X POST "${HARNESS_CODE_API}/repos/${REPO_REF}/pullreq/${PR_NUMBER}/merge" \
-H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"method": "squash",
"source_sha": "abc123def456...",
"title": "feat: Add user authentication (PR #42)",
"message": "Implements user authentication with JWT tokens.\n\nCloses PROJ-123",
"delete_source_branch": true,
"bypass_rules": false,
"dry_run": false
}'
| Method | Description |
|--------|-------------|
| merge | Create a merge commit |
| squash | Squash all commits into one |
| rebase | Rebase commits onto target |
| fast-forward | Fast-forward merge (if possible) |
| Option | Description |
|--------|-------------|
| dry_run | Test mergeability without merging |
| dry_run_rules | Validate branch protection rules |
| bypass_rules | Bypass protection rules (requires permission) |
| delete_source_branch | Delete source branch after merge |
Add these to your shell profile or scripts:
# ~/.bashrc or ~/.zshrc
# Harness Code API base
export HARNESS_CODE_API="${HARNESS_BASE_URL:-https://app.harness.io}/code/api/v1"
# Create PR comment
harness_pr_comment() {
local repo="$1"
local pr="$2"
local text="$3"
curl -s -X POST "${HARNESS_CODE_API}/repos/${repo}/pullreq/${pr}/comments" \
-H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
-d "{\"text\": \"${text}\"}"
}
# Create code comment on specific lines
harness_code_comment() {
local repo="$1"
local pr="$2"
local file="$3"
local line_start="$4"
local line_end="$5"
local text="$6"
curl -s -X POST "${HARNESS_CODE_API}/repos/${repo}/pullreq/${pr}/comments" \
-H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"text\": \"${text}\",
\"path\": \"${file}\",
\"line_start\": ${line_start},
\"line_end\": ${line_end},
\"line_start_new\": true,
\"line_end_new\": true
}"
}
# Submit review
harness_pr_review() {
local repo="$1"
local pr="$2"
local decision="$3" # approved, changereq, reviewed
local commit_sha="$4"
curl -s -X POST "${HARNESS_CODE_API}/repos/${repo}/pullreq/${pr}/reviews" \
-H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"commit_sha\": \"${commit_sha}\",
\"decision\": \"${decision}\"
}"
}
# Approve PR
harness_pr_approve() {
local repo="$1"
local pr="$2"
local commit_sha="$3"
harness_pr_review "$repo" "$pr" "approved" "$commit_sha"
}
# Request changes
harness_pr_request_changes() {
local repo="$1"
local pr="$2"
local commit_sha="$3"
harness_pr_review "$repo" "$pr" "changereq" "$commit_sha"
}
# Merge PR
harness_pr_merge() {
local repo="$1"
local pr="$2"
local method="${3:-squash}"
local source_sha="$4"
local title="$5"
curl -s -X POST "${HARNESS_CODE_API}/repos/${repo}/pullreq/${pr}/merge" \
-H "x-api-key: ${HARNESS_API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"method\": \"${method}\",
\"source_sha\": \"${source_sha}\",
\"title\": \"${title}\",
\"delete_source_branch\": true
}"
}
# Add a comment to PR #42
harness_pr_comment "my-repo" 42 "LGTM! Ready to merge."
# Add code comment on specific lines
harness_code_comment "my-repo" 42 "src/auth.ts" 50 55 "Consider adding null check here"
# Approve PR
harness_pr_approve "my-repo" 42 "abc123def456"
# Request changes
harness_pr_request_changes "my-repo" 42 "abc123def456"
# Merge with squash
harness_pr_merge "my-repo" 42 "squash" "abc123def456" "feat: Add authentication"
import requests
import os
from typing import Optional, Literal
class HarnessCodeAPI:
"""Harness Code REST API client for PR operations."""
def __init__(self, api_key: str = None, base_url: str = None):
self.api_key = api_key or os.environ.get("HARNESS_API_KEY")
self.base_url = base_url or os.environ.get("HARNESS_BASE_URL", "https://app.harness.io")
self.api_url = f"{self.base_url}/code/api/v1"
self.headers = {
"x-api-key": self.api_key,
"Content-Type": "application/json"
}
def create_comment(
self,
repo: str,
pr_number: int,
text: str,
path: Optional[str] = None,
line_start: Optional[int] = None,
line_end: Optional[int] = None,
parent_id: Optional[int] = None
) -> dict:
"""Create a comment on a pull request."""
url = f"{self.api_url}/repos/{repo}/pullreq/{pr_number}/comments"
data = {"text": text}
if parent_id:
data["parent_id"] = parent_id
elif path and line_start:
data.update({
"path": path,
"line_start": line_start,
"line_end": line_end or line_start,
"line_start_new": True,
"line_end_new": True
})
response = requests.post(url, headers=self.headers, json=data)
response.raise_for_status()
return response.json()
def submit_review(
self,
repo: str,
pr_number: int,
commit_sha: str,
decision: Literal["approved", "changereq", "reviewed"]
) -> dict:
"""Submit a review on a pull request."""
url = f"{self.api_url}/repos/{repo}/pullreq/{pr_number}/reviews"
data = {
"commit_sha": commit_sha,
"decision": decision
}
response = requests.post(url, headers=self.headers, json=data)
response.raise_for_status()
return response.json()
def approve(self, repo: str, pr_number: int, commit_sha: str) -> dict:
"""Approve a pull request."""
return self.submit_review(repo, pr_number, commit_sha, "approved")
def request_changes(self, repo: str, pr_number: int, commit_sha: str) -> dict:
"""Request changes on a pull request."""
return self.submit_review(repo, pr_number, commit_sha, "changereq")
def merge(
self,
repo: str,
pr_number: int,
source_sha: str,
method: Literal["merge", "squash", "rebase", "fast-forward"] = "squash",
title: Optional[str] = None,
message: Optional[str] = None,
delete_source_branch: bool = True,
dry_run: bool = False
) -> dict:
"""Merge a pull request."""
url = f"{self.api_url}/repos/{repo}/pullreq/{pr_number}/merge"
data = {
"method": method,
"source_sha": source_sha,
"delete_source_branch": delete_source_branch,
"dry_run": dry_run
}
if title:
data["title"] = title
if message:
data["message"] = message
response = requests.post(url, headers=self.headers, json=data)
response.raise_for_status()
return response.json()
def add_reviewer(self, repo: str, pr_number: int, reviewer_id: int) -> dict:
"""Add a reviewer to a pull request."""
url = f"{self.api_url}/repos/{repo}/pullreq/{pr_number}/reviewers"
data = {"reviewer_id": reviewer_id}
response = requests.post(url, headers=self.headers, json=data)
response.raise_for_status()
return response.json()
# Usage
if __name__ == "__main__":
client = HarnessCodeAPI()
# Add a comment
client.create_comment(
repo="my-app",
pr_number=42,
text="Great implementation! Consider adding unit tests."
)
# Add code comment
client.create_comment(
repo="my-app",
pr_number=42,
text="This could cause a null pointer exception",
path="src/services/auth.ts",
line_start=50,
line_end=52
)
# Approve PR
client.approve(repo="my-app", pr_number=42, commit_sha="abc123")
# Merge with squash
client.merge(
repo="my-app",
pr_number=42,
source_sha="abc123",
method="squash",
title="feat: Add user authentication",
delete_source_branch=True
)
Use these APIs to enable Claude to perform automated code reviews:
from harness_code_api import HarnessCodeAPI
def claude_code_review(repo: str, pr_number: int, jira_key: str):
"""
Claude-powered code review workflow.
1. Fetch PR changes via MCP
2. Analyze code for issues
3. Add comments via REST API
4. Submit review decision
5. Update Jira with review status
"""
client = HarnessCodeAPI()
# Step 1: Get PR details via MCP
pr = harness_get_pull_request(repo_id=repo, pr_number=pr_number)
activities = harness_get_pull_request_activities(repo_id=repo, pr_number=pr_number)
# Step 2: Analyze changes (Claude analyzes the diff)
issues_found = [] # Claude populates this with issues
# Step 3: Add code comments for each issue
for issue in issues_found:
client.create_comment(
repo=repo,
pr_number=pr_number,
text=f"**{issue['severity']}**: {issue['message']}\n\n{issue['suggestion']}",
path=issue['file'],
line_start=issue['line_start'],
line_end=issue['line_end']
)
# Step 4: Submit review decision
if any(i['severity'] == 'critical' for i in issues_found):
client.request_changes(repo, pr_number, pr.source_sha)
review_status = "Changes Requested"
elif issues_found:
client.submit_review(repo, pr_number, pr.source_sha, "reviewed")
review_status = "Reviewed with Comments"
else:
client.approve(repo, pr_number, pr.source_sha)
review_status = "Approved"
# Step 5: Update Jira
jira_add_comment(
issue_key=jira_key,
body=f"""
## Code Review Complete
**PR:** [#{pr_number}]({pr.url})
**Status:** {review_status}
**Issues Found:** {len(issues_found)}
### Issues by Severity
- Critical: {sum(1 for i in issues_found if i['severity'] == 'critical')}
- Warning: {sum(1 for i in issues_found if i['severity'] == 'warning')}
- Info: {sum(1 for i in issues_found if i['severity'] == 'info')}
"""
)
return review_status
The Jira orchestrator automatically creates and links Confluence documentation for all Jira work items. This ensures every issue, sub-issue, and PR has proper documentation.
When work starts on a Jira issue, the following documentation is automatically created:
| Issue Type | Documents Created | |------------|-------------------| | Epic | TDD, Implementation Notes, Runbook, API Docs | | Story | TDD, Implementation Notes | | Task | Implementation Notes | | Sub-task | Implementation Notes (linked to parent) | | Bug | Implementation Notes |
from lib.confluence_doc_linker import ConfluenceDocLinker, DocumentationConfig
# Initialize linker
linker = ConfluenceDocLinker()
# Ensure documentation exists for an issue
docs = linker.ensure_issue_docs("PROJ-123")
# Create docs for sub-issues (linked to parent)
sub_docs = linker.ensure_sub_issue_docs(
parent_jira_key="PROJ-123",
sub_issue_keys=["PROJ-124", "PROJ-125", "PROJ-126"]
)
# Link README to Confluence
linker.link_readme_to_confluence(
readme_path="./README.md",
jira_key="PROJ-123"
)
When creating or reviewing PRs, documentation links are automatically added:
from lib.harness_code_api import HarnessCodeAPI
from lib.confluence_doc_linker import ConfluenceDocLinker
harness = HarnessCodeAPI()
linker = ConfluenceDocLinker()
# Link PR to documentation
linker.link_pr_to_docs(
repo="my-service",
pr_number=42,
jira_key="PROJ-123",
harness_client=harness
)
READMEs are automatically updated with a Documentation section:
## Documentation
**Jira Issue:** [PROJ-123](https://jira.company.com/browse/PROJ-123)
**Confluence Documentation:**
- [Technical Design: PROJ-123](https://confluence.company.com/pages/123)
- [Implementation Notes: PROJ-123](https://confluence.company.com/pages/124)
Create .jira/doc-sync.yml in your workspace:
documentation:
confluence:
base_url: "${CONFLUENCE_BASE_URL}"
space_key: "ENG"
parent_pages:
tdd: "12345678"
impl_notes: "12345679"
auto_create:
enabled: true
on_work_start: true
on_pr_create: true
readme:
auto_update: true
pr:
add_doc_links: true
update_on_merge: true
| Variable | Required | Description |
|----------|----------|-------------|
| CONFLUENCE_BASE_URL | Yes | Confluence instance URL |
| CONFLUENCE_SPACE_KEY | No | Default space (default: ENG) |
| CONFLUENCE_PARENT_PAGE_ID | No | Default parent page for docs |
| JIRA_BASE_URL | Yes | Jira instance URL |
development
This skill should be used when the user asks to "triage issue", "classify ticket", "route jira", "analyze priority", "categorize issue", "determine complexity", "route to agents", or needs guidance on classifying, prioritizing, and routing Jira issues to appropriate agents and workflows.
data-ai
Enriches Jira tasks with comprehensive context, requirements analysis, and technical details through intelligent extraction, dependency mapping, and historical analysis
development
Comprehensive knowledge for creating, managing, and merging pull requests with Jira integration, following best practices for code review, deployment, and team collaboration
tools
This skill should be used when the user asks to "orchestrate jira", "work on issue", "complete jira ticket", "development workflow", "jira automation", "issue lifecycle", "work on story", "fix bug ticket", or needs guidance on coordinating development work through Jira with multi-agent orchestration patterns.