@blario/mcp
Version:
Blar Model Context Protocol server
171 lines (170 loc) • 7.01 kB
JavaScript
import { z } from 'zod';
import { blarAPIClient, BLAR_API_BASE } from '../api-client.js';
export const getPullRequestIssuesSchema = {
pr_number: z.string().describe('Pull request number'),
agent_type: z
.enum([
'debugger',
'optimizer',
'cyber_security',
'design_pattern',
'smell_detector',
])
.optional()
.describe('Filter by agent type'),
severity: z
.enum(['error', 'warning', 'info'])
.optional()
.describe('Filter by severity level'),
repository: z
.string()
.optional()
.describe('Filter by repository name in format <org>/<repo_name> (use when multiple open PRs exist with same number)'),
issue_source: z
.enum(['blar', 'human', 'all'])
.optional()
.default('all')
.describe('Filter by issue source (blar, human, or all)'),
limit: z
.number()
.min(1)
.max(100)
.optional()
.describe('Items per page (max 100, default 5)'),
offset: z
.number()
.min(0)
.optional()
.describe('Starting position for pagination (default 0)'),
};
export async function getPullRequestIssuesHandler({ pr_number, agent_type, severity, repository, issue_source, limit, offset, }) {
// Build the API URL with query parameters
const params = new URLSearchParams();
if (agent_type)
params.append('agent_type', agent_type);
if (severity)
params.append('severity', severity);
if (repository)
params.append('repository', repository);
if (issue_source && issue_source !== 'all')
params.append('issue_source', issue_source);
if (limit)
params.append('limit', limit.toString());
if (offset)
params.append('offset', offset.toString());
const url = `${BLAR_API_BASE}/agents/pull-request/${pr_number}/issues/${params.toString() ? `?${params.toString()}` : ''}`;
const { data, error, status } = await blarAPIClient.makeRequestWithErrorHandling(url);
// Handle multiple PRs error case
if (status === 400 &&
error?.repositories &&
Array.isArray(error.repositories)) {
const errorData = error;
return {
content: [
{
type: 'text',
text: `❌ Multiple Open PRs Found\n` +
`═══════════════════════════════════════\n` +
`PR Number: ${pr_number}\n` +
`Problem: Multiple open PRs found with this number\n\n` +
`Available Repositories:\n` +
errorData.repositories
.map((repo) => ` • ${repo}`)
.join('\n') +
'\n\n' +
`🔧 Solution:\n` +
`Please specify which repository using the 'repository' parameter:\n\n` +
`Examples:\n` +
errorData.repositories
.map((repo) => ` get-pull-request-issues({ pr_number: "${pr_number}", repository: "${repo}" })`)
.join('\n'),
},
],
};
}
// Handle other errors
if (!data || error) {
return {
content: [
{
type: 'text',
text: `Failed to retrieve pull request issues for PR: ${pr_number}`,
},
],
};
}
if (data.results.length === 0) {
return {
content: [
{
type: 'text',
text: `No issues found for pull request: ${pr_number}${repository ? ` in repository: ${repository}` : ''}`,
},
],
};
}
// Calculate pagination info
const currentLimit = limit || 5; // Default limit from API
const currentOffset = offset || 0;
const hasNext = data.next !== null;
const hasPrevious = data.previous !== null;
const totalCount = data.count;
const currentPage = Math.floor(currentOffset / currentLimit) + 1;
const totalPages = Math.ceil(totalCount / currentLimit);
// Format the issues with pagination information
let formatted = `Pull Request ${pr_number} Issues\n`;
formatted += `═══════════════════════════════════════\n`;
formatted += `Total Issues: ${totalCount}\n`;
formatted += `Current Page: ${currentPage} of ${totalPages}\n`;
formatted += `Items per page: ${currentLimit}\n`;
formatted += `Current offset: ${currentOffset}\n`;
formatted += `Showing: ${Math.min(currentOffset + 1, totalCount)}-${Math.min(currentOffset + data.results.length, totalCount)} of ${totalCount}\n`;
if (agent_type)
formatted += `Filtered by agent type: ${agent_type}\n`;
if (severity)
formatted += `Filtered by severity: ${severity}\n`;
if (repository)
formatted += `Filtered by repository: ${repository}\n`;
if (issue_source && issue_source !== 'all')
formatted += `Filtered by source: ${issue_source}\n`;
formatted += `═══════════════════════════════════════\n\n`;
data.results.forEach((issue, idx) => {
const issueNumber = currentOffset + idx + 1;
formatted += `[${issueNumber}] Issue #${issue.id}:\n`;
formatted += ` Text: ${issue.text}\n`;
formatted += ` File: ${issue.file_path} (lines ${issue.start_line}-${issue.end_line})\n`;
formatted += ` Severity: ${issue.severity}\n`;
formatted += ` Source: ${issue.source === 'blar' ? '🤖 Blar' : '👤 Human'}\n`;
if (issue.considerations && issue.considerations.length > 0) {
formatted += ` Considerations:\n`;
issue.considerations.forEach((c, cIdx) => {
formatted += ` [${cIdx + 1}] ${c.title}: ${c.description}\n`;
if (c.code_snippet) {
formatted += ` Code: ${c.code_snippet}\n`;
}
});
}
formatted += ` ---\n`;
});
// Add pagination navigation hints
formatted += `\nPagination Info:\n`;
if (hasPrevious) {
const prevOffset = Math.max(0, currentOffset - currentLimit);
formatted += ` ← Previous page: Use offset=${prevOffset}\n`;
}
if (hasNext) {
const nextOffset = currentOffset + currentLimit;
formatted += ` → Next page: Use offset=${nextOffset}\n`;
}
if (!hasNext && !hasPrevious) {
formatted += ` • All issues are displayed on this page\n`;
}
return {
content: [
{
type: 'text',
text: formatted,
},
],
};
}