bitbucket-mcp
Advanced tools
#!/usr/bin/env node | ||
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; | ||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; | ||
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, InitializeRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; | ||
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js"; | ||
import axios from "axios"; | ||
import winston from "winston"; | ||
import process from "node:process"; | ||
// No console logs, only file logging | ||
// Constants | ||
const LOG_FILE = "bitbucket.log"; | ||
const API_VERSION = "1.1.0"; | ||
const API_PATH = "/rest/api/1.0"; | ||
// Enums | ||
var MergeStrategy; | ||
(function (MergeStrategy) { | ||
MergeStrategy["MERGE_COMMIT"] = "merge-commit"; | ||
MergeStrategy["SQUASH"] = "squash"; | ||
MergeStrategy["FAST_FORWARD"] = "fast-forward"; | ||
})(MergeStrategy || (MergeStrategy = {})); | ||
var ReviewAction; | ||
(function (ReviewAction) { | ||
ReviewAction["APPROVED"] = "APPROVED"; | ||
ReviewAction["REVIEWED"] = "REVIEWED"; | ||
})(ReviewAction || (ReviewAction = {})); | ||
// Configuration for logger | ||
const logger = winston.createLogger({ | ||
level: process.env.LOG_LEVEL || "info", | ||
format: winston.format.combine(winston.format.timestamp(), winston.format.json()), | ||
transports: [ | ||
new winston.transports.File({ filename: "bitbucket.log" }), | ||
...(process.env.DEBUG === "true" ? [new winston.transports.Console()] : []), | ||
], | ||
level: "info", | ||
format: winston.format.json(), | ||
transports: [new winston.transports.File({ filename: LOG_FILE })], | ||
}); | ||
@@ -20,8 +32,7 @@ class BitbucketServer { | ||
api; | ||
cloudApi; | ||
config; | ||
constructor() { | ||
this.server = new Server({ | ||
name: "bitbucket-mcp", | ||
version: "1.0.2", | ||
name: "bitbucket-server-mcp-server", | ||
version: API_VERSION, | ||
}, { | ||
@@ -32,47 +43,43 @@ capabilities: { | ||
}); | ||
// Configuration initiale à partir des variables d'environnement | ||
this.config = { | ||
// Configuration from environment variables | ||
this.config = this.loadConfig(); | ||
// Configuration of Axios instance | ||
this.api = axios.create({ | ||
baseURL: `${this.config.baseUrl}${API_PATH}`, | ||
headers: this.config.token | ||
? { Authorization: `Bearer ${this.config.token}` } | ||
: {}, | ||
auth: this.config.username && this.config.password | ||
? { username: this.config.username, password: this.config.password } | ||
: undefined, | ||
}); | ||
this.setupToolHandlers(); | ||
this.server.onerror = (error) => { | ||
logger.error("[MCP Error]", error); | ||
}; | ||
} | ||
/** | ||
* Loads configuration from environment variables | ||
* @returns BitbucketConfig configuration object | ||
* @throws Error if required environment variables are not set | ||
*/ | ||
loadConfig() { | ||
const config = { | ||
baseUrl: process.env.BITBUCKET_URL ?? "", | ||
token: process.env.BITBUCKET_TOKEN, | ||
username: process.env.BITBUCKET_USERNAME, | ||
appPassword: process.env.BITBUCKET_APP_PASSWORD, | ||
password: process.env.BITBUCKET_PASSWORD, | ||
defaultProject: process.env.BITBUCKET_DEFAULT_PROJECT, | ||
cloudBaseUrl: process.env.BITBUCKET_CLOUD_URL ?? "https://api.bitbucket.org/2.0", | ||
}; | ||
if (!this.config.baseUrl) { | ||
if (!config.baseUrl) { | ||
throw new Error("BITBUCKET_URL is required"); | ||
} | ||
if (!this.config.token && | ||
!(this.config.username && this.config.appPassword)) { | ||
throw new Error("Either BITBUCKET_TOKEN or BITBUCKET_USERNAME/BITBUCKET_APP_PASSWORD is required"); | ||
if (!config.token && !(config.username && config.password)) { | ||
throw new Error("Either BITBUCKET_TOKEN or BITBUCKET_USERNAME/PASSWORD is required"); | ||
} | ||
// Configuration de l'instance Axios | ||
this.api = axios.create({ | ||
baseURL: `${this.config.baseUrl}/rest/api/1.0`, | ||
headers: this.config.token | ||
? { Authorization: `Bearer ${this.config.token}` } | ||
: {}, | ||
auth: this.config.username && this.config.appPassword | ||
? { | ||
username: this.config.username, | ||
password: this.config.appPassword, | ||
} | ||
: undefined, | ||
}); | ||
// Configuration de l'instance Axios pour Bitbucket Cloud | ||
this.cloudApi = axios.create({ | ||
baseURL: this.config.cloudBaseUrl, | ||
headers: this.config.token | ||
? { Authorization: `Bearer ${this.config.token}` } | ||
: {}, | ||
auth: this.config.username && this.config.appPassword | ||
? { | ||
username: this.config.username, | ||
password: this.config.appPassword, | ||
} | ||
: undefined, | ||
}); | ||
this.setupToolHandlers(); | ||
this.server.onerror = (error) => logger.error("[MCP Error]", error); | ||
return config; | ||
} | ||
/** | ||
* Type guard for PullRequestInput | ||
*/ | ||
isPullRequestInput(args) { | ||
@@ -91,11 +98,6 @@ const input = args; | ||
} | ||
/** | ||
* Sets up tool handlers for the MCP server | ||
*/ | ||
setupToolHandlers() { | ||
// Add initialization handler | ||
this.server.setRequestHandler(InitializeRequestSchema, async () => ({ | ||
name: "bitbucket-mcp", | ||
version: "1.0.2", | ||
capabilities: { | ||
tools: {}, | ||
}, | ||
})); | ||
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ | ||
@@ -155,3 +157,3 @@ tools: [ | ||
type: "string", | ||
enum: ["merge-commit", "squash", "fast-forward"], | ||
enum: Object.values(MergeStrategy), | ||
description: "Merge strategy to use", | ||
@@ -225,213 +227,2 @@ }, | ||
}, | ||
{ | ||
name: "list_public_repositories", | ||
description: "List public repositories in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
role: { | ||
type: "string", | ||
description: "Filter by role: admin, contributor, member, owner", | ||
}, | ||
q: { | ||
type: "string", | ||
description: "Query string to filter repositories", | ||
}, | ||
sort: { type: "string", description: "Field to sort results by" }, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "list_workspace_repositories", | ||
description: "List repositories in a workspace in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
workspace: { | ||
type: "string", | ||
description: "Workspace identifier", | ||
}, | ||
role: { | ||
type: "string", | ||
description: "Filter by role: admin, contributor, member, owner", | ||
}, | ||
q: { | ||
type: "string", | ||
description: "Query string to filter repositories", | ||
}, | ||
sort: { type: "string", description: "Field to sort results by" }, | ||
}, | ||
required: ["workspace"], | ||
}, | ||
}, | ||
{ | ||
name: "get_repository", | ||
description: "Get a repository in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
workspace: { | ||
type: "string", | ||
description: "Workspace identifier", | ||
}, | ||
repo_slug: { type: "string", description: "Repository slug" }, | ||
}, | ||
required: ["workspace", "repo_slug"], | ||
}, | ||
}, | ||
{ | ||
name: "list_repository_forks", | ||
description: "List forks of a repository in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
workspace: { | ||
type: "string", | ||
description: "Workspace identifier", | ||
}, | ||
repo_slug: { type: "string", description: "Repository slug" }, | ||
}, | ||
required: ["workspace", "repo_slug"], | ||
}, | ||
}, | ||
{ | ||
name: "list_repository_webhooks", | ||
description: "List webhooks for a repository in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
workspace: { | ||
type: "string", | ||
description: "Workspace identifier", | ||
}, | ||
repo_slug: { type: "string", description: "Repository slug" }, | ||
}, | ||
required: ["workspace", "repo_slug"], | ||
}, | ||
}, | ||
{ | ||
name: "get_repository_webhook", | ||
description: "Get a webhook for a repository in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
workspace: { | ||
type: "string", | ||
description: "Workspace identifier", | ||
}, | ||
repo_slug: { type: "string", description: "Repository slug" }, | ||
uid: { type: "string", description: "Webhook identifier" }, | ||
}, | ||
required: ["workspace", "repo_slug", "uid"], | ||
}, | ||
}, | ||
{ | ||
name: "get_repository_settings_inheritance", | ||
description: "Retrieve the inheritance state for repository settings in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
workspace: { | ||
type: "string", | ||
description: "Workspace identifier", | ||
}, | ||
repo_slug: { type: "string", description: "Repository slug" }, | ||
}, | ||
required: ["workspace", "repo_slug"], | ||
}, | ||
}, | ||
{ | ||
name: "list_repository_group_permissions", | ||
description: "List explicit group permissions for a repository in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
workspace: { | ||
type: "string", | ||
description: "Workspace identifier", | ||
}, | ||
repo_slug: { type: "string", description: "Repository slug" }, | ||
}, | ||
required: ["workspace", "repo_slug"], | ||
}, | ||
}, | ||
{ | ||
name: "get_repository_group_permission", | ||
description: "Get an explicit group permission for a repository in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
workspace: { | ||
type: "string", | ||
description: "Workspace identifier", | ||
}, | ||
repo_slug: { type: "string", description: "Repository slug" }, | ||
group_slug: { type: "string", description: "Group slug" }, | ||
}, | ||
required: ["workspace", "repo_slug", "group_slug"], | ||
}, | ||
}, | ||
{ | ||
name: "list_repository_user_permissions", | ||
description: "List explicit user permissions for a repository in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
workspace: { | ||
type: "string", | ||
description: "Workspace identifier", | ||
}, | ||
repo_slug: { type: "string", description: "Repository slug" }, | ||
}, | ||
required: ["workspace", "repo_slug"], | ||
}, | ||
}, | ||
{ | ||
name: "get_repository_user_permission", | ||
description: "Get an explicit user permission for a repository in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
workspace: { | ||
type: "string", | ||
description: "Workspace identifier", | ||
}, | ||
repo_slug: { type: "string", description: "Repository slug" }, | ||
selected_user_id: { | ||
type: "string", | ||
description: "Selected user identifier", | ||
}, | ||
}, | ||
required: ["workspace", "repo_slug", "selected_user_id"], | ||
}, | ||
}, | ||
{ | ||
name: "list_repository_watchers", | ||
description: "List watchers of a repository in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
workspace: { | ||
type: "string", | ||
description: "Workspace identifier", | ||
}, | ||
repo_slug: { type: "string", description: "Repository slug" }, | ||
}, | ||
required: ["workspace", "repo_slug"], | ||
}, | ||
}, | ||
{ | ||
name: "list_user_repository_permissions", | ||
description: "List repository permissions for a user in Bitbucket Cloud", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
q: { | ||
type: "string", | ||
description: "Query string to filter permissions", | ||
}, | ||
sort: { type: "string", description: "Field to sort results by" }, | ||
}, | ||
}, | ||
}, | ||
], | ||
@@ -445,35 +236,3 @@ })); | ||
const args = request.params.arguments ?? {}; | ||
const pullRequestParams = { | ||
project: args.project ?? this.config.defaultProject, | ||
repository: args.repository, | ||
prId: args.prId, | ||
}; | ||
const workspaceRepoParams = { | ||
workspace: args.workspace, | ||
repo_slug: args.repo_slug, | ||
}; | ||
const webhookParams = { | ||
...workspaceRepoParams, | ||
uid: args.uid, | ||
}; | ||
const groupPermissionParams = { | ||
...workspaceRepoParams, | ||
group_slug: args.group_slug, | ||
}; | ||
const userPermissionParams = { | ||
...workspaceRepoParams, | ||
selected_user_id: args.selected_user_id, | ||
}; | ||
if (!pullRequestParams.project && | ||
[ | ||
"create_pull_request", | ||
"get_pull_request", | ||
"merge_pull_request", | ||
"decline_pull_request", | ||
"add_comment", | ||
"get_diff", | ||
"get_reviews", | ||
].includes(request.params.name)) { | ||
throw new McpError(ErrorCode.InvalidParams, "Project must be provided either as a parameter or through BITBUCKET_DEFAULT_PROJECT environment variable"); | ||
} | ||
const pullRequestParams = this.extractPullRequestParams(args); | ||
switch (request.params.name) { | ||
@@ -503,85 +262,2 @@ case "create_pull_request": | ||
return await this.getReviews(pullRequestParams); | ||
case "list_public_repositories": | ||
return await this.listPublicRepositories({ | ||
role: args.role, | ||
q: args.q, | ||
sort: args.sort, | ||
}); | ||
case "list_workspace_repositories": | ||
if (!workspaceRepoParams.workspace) { | ||
throw new McpError(ErrorCode.InvalidParams, "Workspace must be provided"); | ||
} | ||
return await this.listWorkspaceRepositories(workspaceRepoParams.workspace, { | ||
role: args.role, | ||
q: args.q, | ||
sort: args.sort, | ||
}); | ||
case "get_repository": | ||
if (!workspaceRepoParams.workspace || | ||
!workspaceRepoParams.repo_slug) { | ||
throw new McpError(ErrorCode.InvalidParams, "Workspace and repo_slug must be provided"); | ||
} | ||
return await this.getRepository(workspaceRepoParams); | ||
case "list_repository_forks": | ||
if (!workspaceRepoParams.workspace || | ||
!workspaceRepoParams.repo_slug) { | ||
throw new McpError(ErrorCode.InvalidParams, "Workspace and repo_slug must be provided"); | ||
} | ||
return await this.listRepositoryForks(workspaceRepoParams); | ||
case "list_repository_webhooks": | ||
if (!workspaceRepoParams.workspace || | ||
!workspaceRepoParams.repo_slug) { | ||
throw new McpError(ErrorCode.InvalidParams, "Workspace and repo_slug must be provided"); | ||
} | ||
return await this.listRepositoryWebhooks(workspaceRepoParams); | ||
case "get_repository_webhook": | ||
if (!webhookParams.workspace || | ||
!webhookParams.repo_slug || | ||
!webhookParams.uid) { | ||
throw new McpError(ErrorCode.InvalidParams, "Workspace, repo_slug, and uid must be provided"); | ||
} | ||
return await this.getRepositoryWebhook(webhookParams); | ||
case "get_repository_settings_inheritance": | ||
if (!workspaceRepoParams.workspace || | ||
!workspaceRepoParams.repo_slug) { | ||
throw new McpError(ErrorCode.InvalidParams, "Workspace and repo_slug must be provided"); | ||
} | ||
return await this.getRepositorySettingsInheritance(workspaceRepoParams); | ||
case "list_repository_group_permissions": | ||
if (!workspaceRepoParams.workspace || | ||
!workspaceRepoParams.repo_slug) { | ||
throw new McpError(ErrorCode.InvalidParams, "Workspace and repo_slug must be provided"); | ||
} | ||
return await this.listRepositoryGroupPermissions(workspaceRepoParams); | ||
case "get_repository_group_permission": | ||
if (!groupPermissionParams.workspace || | ||
!groupPermissionParams.repo_slug || | ||
!groupPermissionParams.group_slug) { | ||
throw new McpError(ErrorCode.InvalidParams, "Workspace, repo_slug, and group_slug must be provided"); | ||
} | ||
return await this.getRepositoryGroupPermission(groupPermissionParams); | ||
case "list_repository_user_permissions": | ||
if (!workspaceRepoParams.workspace || | ||
!workspaceRepoParams.repo_slug) { | ||
throw new McpError(ErrorCode.InvalidParams, "Workspace and repo_slug must be provided"); | ||
} | ||
return await this.listRepositoryUserPermissions(workspaceRepoParams); | ||
case "get_repository_user_permission": | ||
if (!userPermissionParams.workspace || | ||
!userPermissionParams.repo_slug || | ||
!userPermissionParams.selected_user_id) { | ||
throw new McpError(ErrorCode.InvalidParams, "Workspace, repo_slug, and selected_user_id must be provided"); | ||
} | ||
return await this.getRepositoryUserPermission(userPermissionParams); | ||
case "list_repository_watchers": | ||
if (!workspaceRepoParams.workspace || | ||
!workspaceRepoParams.repo_slug) { | ||
throw new McpError(ErrorCode.InvalidParams, "Workspace and repo_slug must be provided"); | ||
} | ||
return await this.listRepositoryWatchers(workspaceRepoParams); | ||
case "list_user_repository_permissions": | ||
return await this.listUserRepositoryPermissions({ | ||
q: args.q, | ||
sort: args.sort, | ||
}); | ||
default: | ||
@@ -593,11 +269,40 @@ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); | ||
logger.error("Tool execution error", { error }); | ||
if (axios.isAxiosError(error)) { | ||
throw new McpError(ErrorCode.InternalError, `Bitbucket API error: ${error.response?.data.message ?? error.message}`); | ||
} | ||
throw error; | ||
return this.handleError(error); | ||
} | ||
}); | ||
} | ||
/** | ||
* Extracts pull request parameters from the arguments | ||
*/ | ||
extractPullRequestParams(args) { | ||
const pullRequestParams = { | ||
project: args.project ?? this.config.defaultProject ?? "", | ||
repository: args.repository, | ||
prId: args.prId, | ||
}; | ||
if (!pullRequestParams.project) { | ||
throw new McpError(ErrorCode.InvalidParams, "Project must be provided either as a parameter or through BITBUCKET_DEFAULT_PROJECT environment variable"); | ||
} | ||
return pullRequestParams; | ||
} | ||
/** | ||
* Handles errors and converts them to McpError format | ||
*/ | ||
handleError(error) { | ||
if (error instanceof McpError) { | ||
throw error; | ||
} | ||
if (axios.isAxiosError(error)) { | ||
const axiosError = error; | ||
const statusCode = axiosError.response?.status ?? 500; | ||
const errorMessage = axiosError.response?.data?.message ?? axiosError.message; | ||
throw new McpError(ErrorCode.InternalError, `Bitbucket API error (${statusCode}): ${errorMessage}`); | ||
} | ||
throw new McpError(ErrorCode.InternalError, `Unknown error: ${error instanceof Error ? error.message : String(error)}`); | ||
} | ||
/** | ||
* Creates a new pull request | ||
*/ | ||
async createPullRequest(input) { | ||
const response = await this.api.post(`/projects/${input.project}/repos/${input.repository}/pull-requests`, { | ||
const payload = { | ||
title: input.title, | ||
@@ -622,3 +327,4 @@ description: input.description, | ||
})), | ||
}); | ||
}; | ||
const response = await this.api.post(`/projects/${input.project}/repos/${input.repository}/pull-requests`, payload); | ||
return { | ||
@@ -628,2 +334,5 @@ content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
} | ||
/** | ||
* Gets pull request details | ||
*/ | ||
async getPullRequest(params) { | ||
@@ -636,5 +345,8 @@ const { project, repository, prId } = params; | ||
} | ||
/** | ||
* Merges a pull request | ||
*/ | ||
async mergePullRequest(params, options = {}) { | ||
const { project, repository, prId } = params; | ||
const { message, strategy = "merge-commit" } = options; | ||
const { message, strategy = MergeStrategy.MERGE_COMMIT } = options; | ||
const response = await this.api.post(`/projects/${project}/repos/${repository}/pull-requests/${prId}/merge`, { | ||
@@ -649,2 +361,5 @@ version: -1, | ||
} | ||
/** | ||
* Declines a pull request | ||
*/ | ||
async declinePullRequest(params, message) { | ||
@@ -660,2 +375,5 @@ const { project, repository, prId } = params; | ||
} | ||
/** | ||
* Adds a comment to a pull request | ||
*/ | ||
async addComment(params, options) { | ||
@@ -672,2 +390,5 @@ const { project, repository, prId } = params; | ||
} | ||
/** | ||
* Gets the diff for a pull request | ||
*/ | ||
async getDiff(params, contextLines = 10) { | ||
@@ -683,6 +404,10 @@ const { project, repository, prId } = params; | ||
} | ||
/** | ||
* Gets the reviews for a pull request | ||
*/ | ||
async getReviews(params) { | ||
const { project, repository, prId } = params; | ||
const response = await this.api.get(`/projects/${project}/repos/${repository}/pull-requests/${prId}/activities`); | ||
const reviews = response.data.values.filter((activity) => activity.action === "APPROVED" || activity.action === "REVIEWED"); | ||
const reviews = response.data.values.filter((activity) => activity.action === ReviewAction.APPROVED || | ||
activity.action === ReviewAction.REVIEWED); | ||
return { | ||
@@ -692,128 +417,11 @@ content: [{ type: "text", text: JSON.stringify(reviews, null, 2) }], | ||
} | ||
// Bitbucket Cloud Repository API endpoints implementation | ||
async listPublicRepositories(options) { | ||
const response = await this.cloudApi.get("/repositories", { | ||
params: options, | ||
}); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
async listWorkspaceRepositories(workspace, options) { | ||
const response = await this.cloudApi.get(`/repositories/${workspace}`, { | ||
params: options, | ||
}); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
async getRepository(params) { | ||
const { workspace, repo_slug } = params; | ||
const response = await this.cloudApi.get(`/repositories/${workspace}/${repo_slug}`); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
async listRepositoryForks(params) { | ||
const { workspace, repo_slug } = params; | ||
const response = await this.cloudApi.get(`/repositories/${workspace}/${repo_slug}/forks`); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
async listRepositoryWebhooks(params) { | ||
const { workspace, repo_slug } = params; | ||
const response = await this.cloudApi.get(`/repositories/${workspace}/${repo_slug}/hooks`); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
async getRepositoryWebhook(params) { | ||
const { workspace, repo_slug, uid } = params; | ||
const response = await this.cloudApi.get(`/repositories/${workspace}/${repo_slug}/hooks/${uid}`); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
async getRepositorySettingsInheritance(params) { | ||
const { workspace, repo_slug } = params; | ||
const response = await this.cloudApi.get(`/repositories/${workspace}/${repo_slug}/override-settings`); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
async listRepositoryGroupPermissions(params) { | ||
const { workspace, repo_slug } = params; | ||
const response = await this.cloudApi.get(`/repositories/${workspace}/${repo_slug}/permissions-config/groups`); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
async getRepositoryGroupPermission(params) { | ||
const { workspace, repo_slug, group_slug } = params; | ||
const response = await this.cloudApi.get(`/repositories/${workspace}/${repo_slug}/permissions-config/groups/${group_slug}`); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
async listRepositoryUserPermissions(params) { | ||
const { workspace, repo_slug } = params; | ||
const response = await this.cloudApi.get(`/repositories/${workspace}/${repo_slug}/permissions-config/users`); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
async getRepositoryUserPermission(params) { | ||
const { workspace, repo_slug, selected_user_id } = params; | ||
const response = await this.cloudApi.get(`/repositories/${workspace}/${repo_slug}/permissions-config/users/${selected_user_id}`); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
async listRepositoryWatchers(params) { | ||
const { workspace, repo_slug } = params; | ||
const response = await this.cloudApi.get(`/repositories/${workspace}/${repo_slug}/watchers`); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
async listUserRepositoryPermissions(options) { | ||
const response = await this.cloudApi.get("/user/permissions/repositories", { | ||
params: options, | ||
}); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
/** | ||
* Starts the server | ||
*/ | ||
async run() { | ||
try { | ||
const transport = new StdioServerTransport(); | ||
await this.server.connect(transport); | ||
logger.info("Bitbucket MCP server running on stdio"); | ||
logger.info(`Version: 1.0.2`); | ||
logger.info(`Using Cloud API: ${this.config.cloudBaseUrl}`); | ||
if (this.config.baseUrl) { | ||
logger.info(`Using Server API: ${this.config.baseUrl}`); | ||
} | ||
// Log authentication method used (without revealing secrets) | ||
if (this.config.token) { | ||
logger.info("Authentication: Using token"); | ||
} | ||
else if (this.config.username && this.config.appPassword) { | ||
logger.info(`Authentication: Using basic auth with username ${this.config.username}`); | ||
} | ||
} | ||
catch (error) { | ||
logger.error("Failed to start MCP server", { error }); | ||
process.exit(1); | ||
} | ||
const transport = new StdioServerTransport(); | ||
await this.server.connect(transport); | ||
logger.info("Bitbucket MCP server running on stdio"); | ||
} | ||
} | ||
// No console.log statements in MCP mode | ||
// Handle uncaught exceptions | ||
process.on("uncaughtException", (error) => { | ||
logger.error("Uncaught exception", { error }); | ||
process.exit(1); | ||
}); | ||
// Run the server | ||
const server = new BitbucketServer(); | ||
@@ -820,0 +428,0 @@ server.run().catch((error) => { |
{ | ||
"name": "bitbucket-mcp", | ||
"version": "1.0.5", | ||
"version": "1.1.0", | ||
"description": "Model Context Protocol (MCP) server for Bitbucket Cloud and Server API integration", | ||
@@ -5,0 +5,0 @@ "type": "module", |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
5
-44.44%47931
-41.13%417
-48.45%1
Infinity%