Skip to main content
Glama
project-handlers.ts7.57 kB
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; import { BitbucketApiClient } from '../utils/api-client.js'; import { isListProjectsArgs, isListRepositoriesArgs } from '../types/guards.js'; import { BitbucketServerProject, BitbucketCloudProject, BitbucketServerRepository, BitbucketCloudRepository } from '../types/bitbucket.js'; export class ProjectHandlers { constructor( private apiClient: BitbucketApiClient, private baseUrl: string ) {} async handleListProjects(args: any) { if (!isListProjectsArgs(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for list_projects' ); } const { name, permission, limit = 25, start = 0 } = args; try { let apiPath: string; let params: any = {}; let projects: any[] = []; let totalCount = 0; let nextPageStart: number | null = null; if (this.apiClient.getIsServer()) { // Bitbucket Server API apiPath = `/rest/api/1.0/projects`; params = { limit, start }; if (name) { params.name = name; } if (permission) { params.permission = permission; } const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params }); // Format projects projects = (response.values || []).map((project: BitbucketServerProject) => ({ key: project.key, id: project.id, name: project.name, description: project.description || '', is_public: project.public, type: project.type, url: `${this.baseUrl}/projects/${project.key}` })); totalCount = response.size || projects.length; if (!response.isLastPage && response.nextPageStart !== undefined) { nextPageStart = response.nextPageStart; } } else { // Bitbucket Cloud API apiPath = `/workspaces`; params = { pagelen: limit, page: Math.floor(start / limit) + 1 }; // Cloud uses workspaces, not projects exactly const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params }); projects = (response.values || []).map((workspace: any) => ({ key: workspace.slug, id: workspace.uuid, name: workspace.name, description: '', is_public: !workspace.is_private, type: 'WORKSPACE', url: workspace.links.html.href })); totalCount = response.size || projects.length; if (response.next) { nextPageStart = start + limit; } } return { content: [ { type: 'text', text: JSON.stringify({ projects, total_count: totalCount, start, limit, has_more: nextPageStart !== null, next_start: nextPageStart }, null, 2), }, ], }; } catch (error) { return this.apiClient.handleApiError(error, 'listing projects'); } } async handleListRepositories(args: any) { if (!isListRepositoriesArgs(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid arguments for list_repositories' ); } const { workspace, name, permission, limit = 25, start = 0 } = args; try { let apiPath: string; let params: any = {}; let repositories: any[] = []; let totalCount = 0; let nextPageStart: number | null = null; if (this.apiClient.getIsServer()) { // Bitbucket Server API if (workspace) { // List repos in a specific project apiPath = `/rest/api/1.0/projects/${workspace}/repos`; } else { // List all accessible repos apiPath = `/rest/api/1.0/repos`; } params = { limit, start }; if (name) { params.name = name; } if (permission) { params.permission = permission; } if (!workspace && name) { // When listing all repos and filtering by name params.projectname = name; } const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params }); // Format repositories repositories = (response.values || []).map((repo: BitbucketServerRepository) => ({ slug: repo.slug, id: repo.id, name: repo.name, description: repo.description || '', project_key: repo.project.key, project_name: repo.project.name, state: repo.state, is_public: repo.public, is_forkable: repo.forkable, clone_urls: { http: repo.links.clone.find(c => c.name === 'http')?.href || '', ssh: repo.links.clone.find(c => c.name === 'ssh')?.href || '' }, url: `${this.baseUrl}/projects/${repo.project.key}/repos/${repo.slug}` })); totalCount = response.size || repositories.length; if (!response.isLastPage && response.nextPageStart !== undefined) { nextPageStart = response.nextPageStart; } } else { // Bitbucket Cloud API if (workspace) { // List repos in a specific workspace apiPath = `/repositories/${workspace}`; } else { // Cloud doesn't support listing all repos without workspace // We'll return an error message return { content: [ { type: 'text', text: JSON.stringify({ error: 'Bitbucket Cloud requires a workspace parameter to list repositories. Please provide a workspace.' }, null, 2), }, ], isError: true, }; } params = { pagelen: limit, page: Math.floor(start / limit) + 1 }; const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params }); repositories = (response.values || []).map((repo: BitbucketCloudRepository) => ({ slug: repo.slug, id: repo.uuid, name: repo.name, description: repo.description || '', project_key: repo.project?.key || '', project_name: repo.project?.name || '', state: 'AVAILABLE', is_public: !repo.is_private, is_forkable: true, clone_urls: { http: repo.links.clone.find(c => c.name === 'https')?.href || '', ssh: repo.links.clone.find(c => c.name === 'ssh')?.href || '' }, url: repo.links.html.href })); totalCount = response.size || repositories.length; if (response.next) { nextPageStart = start + limit; } } return { content: [ { type: 'text', text: JSON.stringify({ repositories, total_count: totalCount, start, limit, has_more: nextPageStart !== null, next_start: nextPageStart, workspace: workspace || 'all' }, null, 2), }, ], }; } catch (error) { return this.apiClient.handleApiError(error, workspace ? `listing repositories in ${workspace}` : 'listing repositories'); } } }

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/pdogra1299/bitbucket-mcp-server'

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