@jwalsh/mcp-server-qrcode
Version:
Model Context Protocol server for generating QR codes
293 lines (292 loc) • 11.7 kB
JavaScript
import server from './index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { program } from 'commander';
import { generateQRCode } from './qrcode.js';
import { writeFile } from 'fs/promises';
import { promisify } from 'util';
import { exec } from 'child_process';
import path from 'path';
const execAsync = promisify(exec);
// Check if we have stdin data (piped input)
const hasStdin = !process.stdin.isTTY;
// Configure the main CLI
program.name('mcp-server-qrcode').description('MCP server for generating QR codes').version('0.3.6');
// Generate command
program
.command('generate')
.description('Generate a QR code')
.option('-c, --content <content>', 'Content to encode in the QR code')
.option('-s, --size <size>', 'Size of the QR code (100-1000 pixels)', '200')
.option('-e, --error-correction <level>', 'Error correction level (L, M, Q, H)', 'M')
.option('-o, --output <file>', 'Output file path (defaults to terminal output)')
.option('-f, --format <format>', 'Output format (text or image)', 'text')
.action(async (options) => {
try {
const content = options.content;
if (!content) {
console.error('Error: No content provided for QR code generation');
process.exit(1);
}
const size = parseInt(options.size, 10);
const format = options.format === 'image' ? 'base64' : 'terminal';
// Generate QR code
const result = await generateQRCode({
content,
size,
errorCorrectionLevel: options.errorCorrection,
format,
});
// Handle output
if (options.output) {
if (format === 'base64') {
// Write image to file
const buffer = Buffer.from(result.data, 'base64');
await writeFile(options.output, buffer);
console.log(`QR code image saved to ${options.output}`);
}
else {
// Write text to file
await writeFile(options.output, result.data);
console.log(`QR code text saved to ${options.output}`);
}
}
else {
// Output to terminal
console.log(result.data);
}
}
catch (error) {
console.error('Error generating QR code:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// Resource command
program
.command('resource <uri>')
.description('Generate QR code from MCP resource URI')
.option('-o, --output <file>', 'Output file path (defaults to terminal display)')
.option('-v, --view', 'View the generated QR code image immediately (if supported)')
.action(async (uri, options) => {
try {
// Validate URI format
if (!uri.startsWith('qrcode://')) {
console.error('Error: Resource URI must start with qrcode://');
console.log('Examples:');
console.log(' qrcode://sample');
console.log(' qrcode://hello-world');
console.log(' qrcode://https://example.com?size=300&level=H');
process.exit(1);
}
// Determine resource type and parameters
let resourceUri = uri;
let isSampleResource = false;
let content = '';
let size = 200;
let errorCorrectionLevel = 'M';
if (uri === 'qrcode://sample') {
isSampleResource = true;
}
else {
// Custom resource - Extract content and parameters from the URI
const contentMatch = uri.match(/^qrcode:\/\/(.+?)(?:\?|$)/);
content = contentMatch ? contentMatch[1] : '';
// Extract query parameters
const queryString = uri.includes('?') ? uri.split('?')[1] : '';
const queryParams = new URLSearchParams(queryString);
// Parse size parameter if present
if (queryParams.has('size')) {
const sizeParam = parseInt(queryParams.get('size'), 10);
if (!isNaN(sizeParam)) {
size = sizeParam;
}
}
// Parse error correction level if present
const levelParam = queryParams.get('level');
if (levelParam && ['L', 'M', 'Q', 'H'].includes(levelParam)) {
errorCorrectionLevel = levelParam;
}
}
// Generate QR code directly based on requested resource
let result;
if (isSampleResource) {
// Sample QR code
const qrResult = await generateQRCode({
content: 'https://github.com/jwalsh/mcp-server-qrcode',
size: 200,
errorCorrectionLevel: 'M',
format: 'base64',
});
// Create a formatted result mimicking the resource API
result = {
contents: [
{
text: 'Sample QR Code for MCP QR Code Server',
uri: 'qrcode://sample',
blob: qrResult.data,
mimeType: 'image/png',
},
],
};
}
else {
// Custom QR code
if (!content) {
console.error('Error: Content parameter is required');
process.exit(1);
}
try {
// Generate QR code
const qrResult = await generateQRCode({
content: decodeURIComponent(content),
size,
errorCorrectionLevel,
format: 'base64',
});
// Create a formatted result mimicking the resource API
result = {
contents: [
{
text: `QR Code for "${decodeURIComponent(content)}"`,
uri: resourceUri,
blob: qrResult.data,
mimeType: 'image/png',
},
],
};
}
catch (error) {
console.error('Error generating QR code:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
}
if (result.isError) {
console.error('Error:', result.contents[0].text);
process.exit(1);
}
// Find image content in the result
const imageContent = result.contents.find(item => item.blob && item.mimeType?.includes('image'));
if (!imageContent) {
console.error('Error: No image content found in resource result');
process.exit(1);
}
// Handle output options
if (options.output) {
// Write image to file
const buffer = Buffer.from(imageContent.blob, 'base64');
await writeFile(options.output, buffer);
console.log(`QR code image saved to ${options.output}`);
// Open image if view flag is set
if (options.view) {
try {
const absolutePath = path.resolve(options.output);
// Try to open with different commands based on platform
if (process.platform === 'darwin') {
await execAsync(`open "${absolutePath}"`);
}
else if (process.platform === 'win32') {
await execAsync(`start "" "${absolutePath}"`);
}
else {
await execAsync(`xdg-open "${absolutePath}"`);
}
}
catch (error) {
console.error('Failed to open image:', error instanceof Error ? error.message : String(error));
}
}
}
else {
// No output file, print information
console.log('QR Code generated successfully.');
console.log('Resource URI:', uri);
console.log('Use --output flag to save the QR code as an image file.');
}
}
catch (error) {
console.error('Error generating resource:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// Server command (default)
program
.command('server')
.description('Start the QR Code MCP server (default if no command is specified)')
.option('-v, --verbose', 'Enable verbose logging')
.action(async (options) => {
try {
const verbose = options.verbose || false;
console.log(`Starting QR Code MCP server with verbose: ${verbose}`);
// Create a stdio transport for the server
const transport = new StdioServerTransport();
// Connect the server to the transport
await server.connect(transport);
console.log('QR Code MCP server is running');
}
catch (error) {
console.error('Failed to start server:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
// Handle stdin piping
if (hasStdin) {
let inputData = '';
process.stdin.on('data', chunk => {
inputData += chunk;
});
process.stdin.on('end', async () => {
try {
const content = inputData.trim();
if (!content) {
console.error('Error: No content provided for QR code generation');
process.exit(1);
}
// Generate QR code in terminal-friendly format
const result = await generateQRCode({
content,
size: 200,
errorCorrectionLevel: 'M',
format: 'terminal',
});
// Output the QR code to the terminal
console.log(result.data);
}
catch (error) {
console.error('Error generating QR code:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
}
else {
// Handle older CLI interface for backward compatibility
program
.option('-v, --verbose', 'Enable verbose logging')
.option('-g, --generate <content>', 'Generate QR code from the provided content')
.option('-s, --size <size>', 'Size of the QR code (100-1000 pixels)', '200')
.option('-e, --error-correction <level>', 'Error correction level (L, M, Q, H)', 'M');
// Support original behavior for backward compatibility
program.action(async (options) => {
// Backward compatibility for old CLI interface
if (options.generate) {
const result = await generateQRCode({
content: options.generate,
size: parseInt(options.size, 10),
errorCorrectionLevel: options.errorCorrection,
format: 'terminal',
});
console.log(result.data);
}
else if (!program.args.length || program.args[0] === 'server') {
// Start in server mode if no other command or specifically server command
const verbose = options.verbose || false;
console.log(`Starting QR Code MCP server with verbose: ${verbose}`);
// Create a stdio transport for the server
const transport = new StdioServerTransport();
// Connect the server to the transport
await server.connect(transport);
console.log('QR Code MCP server is running');
}
});
// Parse command line arguments
program.parse();
}