@rashidazarang/aptly-mcp
Version:
Model Context Protocol server for Aptly package repository management - enables AI assistants to manage Debian repositories
280 lines • 12.5 kB
JavaScript
import { addPackagesSchema, searchPackagesSchema, formatValidationError } from '../utils/validation.js';
import { z } from 'zod';
export function createPackageTools(client) {
return [
{
name: 'aptly_list_packages',
description: 'List packages in a repository',
inputSchema: {
type: 'object',
properties: {
repoName: {
type: 'string',
description: 'Name of the repository to list packages from'
},
query: {
type: 'string',
description: 'Optional search query to filter packages'
}
},
required: ['repoName']
}
},
{
name: 'aptly_search_packages',
description: 'Search for packages across repositories or in a specific repository',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query (package name, description, etc.)'
},
repoName: {
type: 'string',
description: 'Optional repository name to search within (if not provided, searches all repositories)'
},
format: {
type: 'string',
enum: ['compact', 'details'],
description: 'Output format for search results',
default: 'compact'
}
},
required: ['query']
}
},
{
name: 'aptly_add_packages',
description: 'Add packages to a repository from uploaded files',
inputSchema: {
type: 'object',
properties: {
repoName: {
type: 'string',
description: 'Name of the repository to add packages to'
},
directory: {
type: 'string',
description: 'Upload directory containing the package files'
},
files: {
type: 'array',
items: { type: 'string' },
description: 'Optional list of specific files to add (if not provided, adds all files from directory)'
}
},
required: ['repoName', 'directory']
}
},
{
name: 'aptly_list_uploaded_files',
description: 'List uploaded files available for adding to repositories',
inputSchema: {
type: 'object',
properties: {
directory: {
type: 'string',
description: 'Optional directory name to list files from (if not provided, lists all directories)'
}
},
required: []
}
},
{
name: 'aptly_delete_uploaded_files',
description: 'Delete uploaded files or directories',
inputSchema: {
type: 'object',
properties: {
directory: {
type: 'string',
description: 'Directory name to delete'
},
filename: {
type: 'string',
description: 'Optional specific filename to delete (if not provided, deletes entire directory)'
}
},
required: ['directory']
}
}
];
}
export async function handlePackageTool(client, name, arguments_) {
try {
switch (name) {
case 'aptly_list_packages': {
const args = z.object({
repoName: z.string(),
query: z.string().optional()
}).parse(arguments_);
const packages = await client.listPackages(args.repoName, args.query);
if (packages.length === 0) {
return {
content: [{
type: 'text',
text: args.query
? `No packages found matching '${args.query}' in repository '${args.repoName}'.`
: `Repository '${args.repoName}' contains no packages.`
}]
};
}
const packageList = packages.map(pkg => {
return `• ${pkg.Package || pkg.Key} (${pkg.Version || 'unknown version'})` +
(pkg.Architecture ? ` [${pkg.Architecture}]` : '') +
(pkg.Description ? ` - ${pkg.Description.substring(0, 80)}${pkg.Description.length > 80 ? '...' : ''}` : '');
}).join('\n');
return {
content: [{
type: 'text',
text: `Found ${packages.length} package(s) in repository '${args.repoName}':\n\n${packageList}`
}]
};
}
case 'aptly_search_packages': {
const args = searchPackagesSchema.parse(arguments_);
if (args.repoName) {
// Search in specific repository
const packages = await client.listPackages(args.repoName, args.query);
if (packages.length === 0) {
return {
content: [{
type: 'text',
text: `No packages found matching '${args.query}' in repository '${args.repoName}'.`
}]
};
}
const packageList = packages.map(pkg => {
if (args.format === 'details') {
return `Package: ${pkg.Package || pkg.Key}\n` +
`Version: ${pkg.Version || 'unknown'}\n` +
`Architecture: ${pkg.Architecture || 'unknown'}\n` +
`Description: ${pkg.Description || 'No description'}\n` +
`Maintainer: ${pkg.Maintainer || 'Unknown'}\n` +
`Size: ${pkg.Size || 'Unknown'} bytes\n`;
}
else {
return `• ${pkg.Package || pkg.Key} (${pkg.Version || 'unknown'})` +
(pkg.Architecture ? ` [${pkg.Architecture}]` : '');
}
}).join('\n');
return {
content: [{
type: 'text',
text: `Found ${packages.length} package(s) matching '${args.query}' in repository '${args.repoName}':\n\n${packageList}`
}]
};
}
else {
// Search across all repositories
const repos = await client.listRepositories();
let totalPackages = 0;
let results = [];
for (const repo of repos) {
try {
const packages = await client.listPackages(repo.Name, args.query);
if (packages.length > 0) {
totalPackages += packages.length;
results.push(`\nRepository: ${repo.Name} (${packages.length} matches)`);
const packageList = packages.map(pkg => {
return ` • ${pkg.Package || pkg.Key} (${pkg.Version || 'unknown'})` +
(pkg.Architecture ? ` [${pkg.Architecture}]` : '');
}).join('\n');
results.push(packageList);
}
}
catch (error) {
// Skip repositories that can't be searched
continue;
}
}
if (totalPackages === 0) {
return {
content: [{
type: 'text',
text: `No packages found matching '${args.query}' across all repositories.`
}]
};
}
return {
content: [{
type: 'text',
text: `Found ${totalPackages} package(s) matching '${args.query}' across ${repos.length} repositories:${results.join('\n')}`
}]
};
}
}
case 'aptly_add_packages': {
const args = addPackagesSchema.parse(arguments_);
await client.addPackages(args.repoName, args.directory, args.files);
const fileCount = args.files ? args.files.length : 'all';
return {
content: [{
type: 'text',
text: `Successfully added ${fileCount} package(s) from directory '${args.directory}' to repository '${args.repoName}'.`
}]
};
}
case 'aptly_list_uploaded_files': {
const args = z.object({
directory: z.string().optional()
}).parse(arguments_);
const files = await client.listUploadedFiles(args.directory);
if (Object.keys(files).length === 0) {
return {
content: [{
type: 'text',
text: args.directory
? `No files found in directory '${args.directory}'.`
: 'No uploaded files found.'
}]
};
}
let result = '';
for (const [dir, fileList] of Object.entries(files)) {
result += `\nDirectory: ${dir}\n`;
if (fileList.length === 0) {
result += ' (empty)\n';
}
else {
fileList.forEach(file => {
result += ` • ${file.filename} (${file.size} bytes)\n`;
});
}
}
return {
content: [{
type: 'text',
text: `Uploaded files:${result}`
}]
};
}
case 'aptly_delete_uploaded_files': {
const args = z.object({
directory: z.string(),
filename: z.string().optional()
}).parse(arguments_);
await client.deleteUploadedFiles(args.directory, args.filename);
const target = args.filename
? `file '${args.filename}' from directory '${args.directory}'`
: `directory '${args.directory}' and all its files`;
return {
content: [{
type: 'text',
text: `Successfully deleted ${target}.`
}]
};
}
default:
throw new Error(`Unknown package tool: ${name}`);
}
}
catch (error) {
if (error instanceof z.ZodError) {
throw new Error(`Validation error: ${formatValidationError(error)}`);
}
throw error;
}
}
//# sourceMappingURL=package-tools.js.map