github-repos-manager-mcp
Version:
Model Context Protocol (MCP) server that enables your MCP client (e.g., Claude Desktop, Cline, Roo Code, Cursor, Windsurf, etc.) to interact with GitHub repositories using your GitHub personal access token.
290 lines (276 loc) • 10.3 kB
JavaScript
const { getOwnerRepo } = require("../utils/shared-utils.cjs");
/**
* @typedef {import('../services/github-api.cjs').GitHubRepo} GitHubRepo
*/
/**
* Lists repository deploy keys.
* @param {object} args - The arguments for the tool.
* @param {string} [args.owner] - Repository owner.
* @param {string} [args.repo] - Repository name.
* @param {number} [args.per_page=30] - Number of deploy keys per page.
* @param {object} apiService - The GitHub API service instance.
* @returns {Promise<object>} - The result of the operation.
*/
async function list_deploy_keys(args, apiService) {
try {
const { owner, repo } = getOwnerRepo(args, {});
const { per_page } = args;
const keys = await apiService.listDeployKeys(owner, repo, per_page);
return { success: true, data: keys };
} catch (error) {
return { success: false, message: error.message, error: error.stack };
}
}
/**
* Adds a new deploy key to a repository.
* @param {object} args - The arguments for the tool.
* @param {string} [args.owner] - Repository owner.
* @param {string} [args.repo] - Repository name.
* @param {string} args.title - A name for the key.
* @param {string} args.key - The public SSH key.
* @param {boolean} [args.read_only=false] - If true, the key will only have read-only access.
* @param {GitHubRepo} defaultRepo - The default repository.
* @returns {Promise<object>} - The result of the operation.
*/
async function create_deploy_key(args, apiService) {
try {
const { owner, repo } = getOwnerRepo(args, {});
const { title, key, read_only } = args;
if (!title || !key) {
return {
success: false,
message: "Missing required arguments: title and key.",
};
}
const newKey = await apiService.createDeployKey(
owner,
repo,
title,
key,
read_only
);
return { success: true, data: newKey };
} catch (error) {
return { success: false, message: error.message, error: error.stack };
}
}
/**
* Removes a deploy key from a repository.
* @param {object} args - The arguments for the tool.
* @param {string} [args.owner] - Repository owner.
* @param {string} [args.repo] - Repository name.
* @param {number} args.key_id - The ID of the key to delete.
* @param {GitHubRepo} defaultRepo - The default repository.
* @returns {Promise<object>} - The result of the operation.
*/
async function delete_deploy_key(args, apiService) {
try {
const { owner, repo } = getOwnerRepo(args, {});
const { key_id } = args;
if (!key_id) {
return { success: false, message: "Missing required argument: key_id." };
}
await apiService.deleteDeployKey(owner, repo, key_id);
return {
success: true,
message: `Deploy key ${key_id} deleted successfully.`,
};
} catch (error) {
return { success: false, message: error.message, error: error.stack };
}
}
/**
* Lists repository webhooks.
* @param {object} args - The arguments for the tool.
* @param {string} [args.owner] - Repository owner.
* @param {string} [args.repo] - Repository name.
* @param {number} [args.per_page=30] - Number of webhooks per page.
* @param {GitHubRepo} defaultRepo - The default repository.
* @returns {Promise<object>} - The result of the operation.
*/
async function list_webhooks(args, apiService) {
try {
const { owner, repo } = getOwnerRepo(args, {});
const { per_page } = args;
const webhooks = await apiService.listWebhooks(owner, repo, per_page);
return { success: true, data: webhooks };
} catch (error) {
return { success: false, message: error.message, error: error.stack };
}
}
/**
* Creates a new webhook for a repository.
* @param {object} args - The arguments for the tool.
* @param {string} [args.owner] - Repository owner.
* @param {string} [args.repo] - Repository name.
* @param {object} args.config - Webhook configuration.
* @param {string} args.config.url - The URL to which the payloads will be delivered.
* @param {string} [args.config.content_type="json"] - The media type used to serialize the payloads.
* @param {string} [args.config.secret] - Secret for HMAC digest.
* @param {string|number} [args.config.insecure_ssl="0"] - Whether to verify SSL.
* @param {string[]} [args.events=["push"]] - Events the hook is triggered for.
* @param {boolean} [args.active=true] - Whether the webhook is active.
* @param {GitHubRepo} defaultRepo - The default repository.
* @returns {Promise<object>} - The result of the operation.
*/
async function create_webhook(args, apiService) {
try {
const { owner, repo } = getOwnerRepo(args, {});
const { config, events, active } = args;
if (!config || !config.url) {
return {
success: false,
message: "Missing required argument: config.url.",
};
}
// TODO: Add webhook signature validation logic if secret is provided (potentially in a utility)
const newWebhook = await apiService.createWebhook(
owner,
repo,
config,
events,
active
);
return { success: true, data: newWebhook };
} catch (error) {
return { success: false, message: error.message, error: error.stack };
}
}
/**
* Modifies an existing webhook's configuration.
* @param {object} args - The arguments for the tool.
* @param {string} [args.owner] - Repository owner.
* @param {string} [args.repo] - Repository name.
* @param {number} args.hook_id - The ID of the webhook to update.
* @param {object} [args.config] - Webhook configuration to update.
* @param {string[]} [args.events] - Replaces existing events.
* @param {string[]} [args.add_events] - Events to add.
* @param {string[]} [args.remove_events] - Events to remove.
* @param {boolean} [args.active] - Whether the webhook is active.
* @param {GitHubRepo} defaultRepo - The default repository.
* @returns {Promise<object>} - The result of the operation.
*/
async function edit_webhook(args, apiService) {
try {
const { owner, repo } = getOwnerRepo(args, {});
const { hook_id, config, events, add_events, remove_events, active } = args;
if (!hook_id) {
return { success: false, message: "Missing required argument: hook_id." };
}
const updateData = { config, events, add_events, remove_events, active };
// Filter out undefined properties from updateData
const filteredUpdateData = Object.fromEntries(
Object.entries(updateData).filter(([_, v]) => v !== undefined)
);
if (Object.keys(filteredUpdateData).length === 0) {
return {
success: false,
message: "No update parameters provided for the webhook.",
};
}
const updatedWebhook = await apiService.updateWebhook(
owner,
repo,
hook_id,
filteredUpdateData
);
return { success: true, data: updatedWebhook };
} catch (error) {
return { success: false, message: error.message, error: error.stack };
}
}
/**
* Removes a webhook from a repository.
* @param {object} args - The arguments for the tool.
* @param {string} [args.owner] - Repository owner.
* @param {string} [args.repo] - Repository name.
* @param {number} args.hook_id - The ID of the webhook to delete.
* @param {GitHubRepo} defaultRepo - The default repository.
* @returns {Promise<object>} - The result of the operation.
*/
async function delete_webhook(args, apiService) {
try {
const { owner, repo } = getOwnerRepo(args, {});
const { hook_id } = args;
if (!hook_id) {
return { success: false, message: "Missing required argument: hook_id." };
}
await apiService.deleteWebhook(owner, repo, hook_id);
return {
success: true,
message: `Webhook ${hook_id} deleted successfully.`,
};
} catch (error) {
return { success: false, message: error.message, error: error.stack };
}
}
/**
* Lists repository secrets (names only).
* @param {object} args - The arguments for the tool.
* @param {string} [args.owner] - Repository owner.
* @param {string} [args.repo] - Repository name.
* @param {number} [args.per_page=30] - Number of secrets per page.
* @param {GitHubRepo} defaultRepo - The default repository.
* @returns {Promise<object>} - The result of the operation.
*/
async function list_secrets(args, apiService) {
try {
const { owner, repo } = getOwnerRepo(args, {});
const { per_page } = args;
const secrets = await apiService.listSecrets(owner, repo, per_page);
// Ensure secret values are not returned, only names and metadata
return { success: true, data: secrets };
} catch (error) {
return { success: false, message: error.message, error: error.stack };
}
}
/**
* Creates or updates a repository secret.
* @param {object} args - The arguments for the tool.
* @param {string} [args.owner] - Repository owner.
* @param {string} [args.repo] - Repository name.
* @param {string} args.secret_name - The name of the secret.
* @param {string} args.encrypted_value - Value for your secret, encrypted with LibSodium.
* @param {string} args.key_id - ID of the key used to encrypt the secret.
* @param {GitHubRepo} defaultRepo - The default repository.
* @returns {Promise<object>} - The result of the operation.
*/
async function update_secret(args, apiService) {
try {
const { owner, repo } = getOwnerRepo(args, {});
const { secret_name, encrypted_value, key_id } = args;
if (!secret_name || !encrypted_value || !key_id) {
return {
success: false,
message:
"Missing required arguments: secret_name, encrypted_value, and key_id.",
};
}
// The API call itself handles create or update.
// GitHub API for creating/updating secrets does not return the secret value, only status.
await apiService.createOrUpdateSecret(
owner,
repo,
secret_name,
encrypted_value,
key_id
);
return {
success: true,
message: `Secret ${secret_name} updated successfully.`,
};
} catch (error) {
return { success: false, message: error.message, error: error.stack };
}
}
module.exports = {
list_deploy_keys,
create_deploy_key,
delete_deploy_key,
list_webhooks,
create_webhook,
edit_webhook,
delete_webhook,
list_secrets,
update_secret,
};