url-builder-mcp
Version:
URL Builder MCP Server - Professional URL construction with intelligent parameter handling for Claude Desktop
359 lines (337 loc) • 11.8 kB
text/typescript
#!/usr/bin/env node
/**
* URL Builder MCP Server
*
* MCP server that provides URL building tools following project-specific rules
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { buildBookingUrl, buildInternalUrl, buildCompleteUrl, validateUrlParams } from './urlBuilder.js';
import { UrlParams } from './types.js';
class UrlBuilderServer {
private server: Server;
constructor() {
this.server = new Server({
name: 'url-builder-mcp',
version: '1.0.0',
});
this.setupToolHandlers();
this.setupErrorHandling();
}
private setupErrorHandling(): void {
this.server.onerror = error => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers(): void {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'build_booking_url',
description: 'Build external booking URL with query parameters',
inputSchema: {
type: 'object',
properties: {
checkIn: {
type: 'string',
description: 'Check-in date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
checkOut: {
type: 'string',
description: 'Check-out date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
guestName: {
type: 'string',
description: 'Guest name (required)',
},
guestPhone: {
type: 'string',
description: 'Guest phone (required)',
},
adults: {
type: 'number',
description: 'Number of adults (optional, default: 2)',
minimum: 1,
maximum: 20,
},
children: {
type: 'number',
description: 'Number of children (optional, default: 0)',
minimum: 0,
maximum: 10,
},
roomType: {
type: 'string',
description: 'Room type name (optional, e.g., "garden-deluxe")',
},
guestEmail: {
type: 'string',
description: 'Guest email (optional)',
},
addUtmTracking: {
type: 'boolean',
description: 'Add UTM tracking parameters for traffic analysis (default: true)',
},
utmCampaign: {
type: 'string',
description: 'Custom UTM campaign name (default: "url-builder-mcp")',
},
utmSource: {
type: 'string',
description: 'Custom UTM source (default: "claude-desktop")',
},
utmMedium: {
type: 'string',
description: 'Custom UTM medium (default: "ai-assistant")',
},
},
required: ['checkIn', 'checkOut', 'guestName', 'guestPhone'],
},
},
{
name: 'build_internal_url',
description: 'Build internal project URL with query parameters and UTM tracking',
inputSchema: {
type: 'object',
properties: {
checkIn: {
type: 'string',
description: 'Check-in date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
checkOut: {
type: 'string',
description: 'Check-out date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
adults: {
type: 'number',
description: 'Number of adults (optional, default: 2)',
minimum: 1,
maximum: 20,
},
children: {
type: 'number',
description: 'Number of children (optional, default: 0)',
minimum: 0,
maximum: 10,
},
roomType: {
type: 'string',
description: 'Room type name (optional)',
},
guestName: {
type: 'string',
description: 'Guest name (optional)',
},
guestEmail: {
type: 'string',
description: 'Guest email (optional)',
},
guestPhone: {
type: 'string',
description: 'Guest phone (optional)',
},
useBaseDomain: {
type: 'boolean',
description: 'Use base domain for complete URL (default: false)',
},
addUtmTracking: {
type: 'boolean',
description: 'Add UTM tracking parameters (default: true)',
},
},
required: ['checkIn', 'checkOut'],
},
},
{
name: 'build_complete_url',
description: 'Build complete URL using configurable base domain with UTM tracking',
inputSchema: {
type: 'object',
properties: {
relativePath: {
type: 'string',
description: 'Relative path to append to base domain (e.g., "/api/bookings")',
},
checkIn: {
type: 'string',
description: 'Check-in date in YYYY-MM-DD format (optional)',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
checkOut: {
type: 'string',
description: 'Check-out date in YYYY-MM-DD format (optional)',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
adults: {
type: 'number',
description: 'Number of adults (optional)',
minimum: 1,
maximum: 20,
},
children: {
type: 'number',
description: 'Number of children (optional)',
minimum: 0,
maximum: 10,
},
addUtmTracking: {
type: 'boolean',
description: 'Add UTM tracking parameters (default: true)',
},
},
required: ['relativePath'],
},
},
{
name: 'validate_url_params',
description: 'Validate URL parameters without building URLs',
inputSchema: {
type: 'object',
properties: {
checkIn: {
type: 'string',
description: 'Check-in date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
checkOut: {
type: 'string',
description: 'Check-out date in YYYY-MM-DD format',
pattern: '^\\d{4}-\\d{2}-\\d{2}$',
},
adults: {
type: 'number',
description: 'Number of adults (optional)',
minimum: 1,
maximum: 20,
},
children: {
type: 'number',
description: 'Number of children (optional)',
minimum: 0,
maximum: 10,
},
roomType: {
type: 'string',
description: 'Room type name (optional, e.g., "garden-deluxe")',
},
guestName: {
type: 'string',
description: 'Guest name (optional)',
},
guestEmail: {
type: 'string',
description: 'Guest email (optional)',
},
guestPhone: {
type: 'string',
description: 'Guest phone (optional)',
},
},
required: ['checkIn', 'checkOut'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async request => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'build_booking_url': {
const { addUtmTracking, utmCampaign, utmSource, utmMedium, ...params } = args as any;
// Build options with UTM parameters
const options = {
addUtmTracking: addUtmTracking !== false, // Default to true
customUtm: {
...(utmCampaign && { utm_campaign: utmCampaign }),
...(utmSource && { utm_source: utmSource }),
...(utmMedium && { utm_medium: utmMedium }),
},
};
const url = buildBookingUrl(params as UrlParams, options);
return {
content: [
{
type: 'text',
text: `Booking URL with UTM tracking: ${url}`,
},
],
};
}
case 'build_internal_url': {
const { useBaseDomain, addUtmTracking, ...params } = args as any;
const options = {
useBaseDomain: useBaseDomain || false,
addUtmTracking: addUtmTracking !== false,
};
const url = buildInternalUrl(params as UrlParams, options);
return {
content: [
{
type: 'text',
text: `Internal URL with UTM tracking: ${url}`,
},
],
};
}
case 'build_complete_url': {
const { relativePath, addUtmTracking, ...params } = args as any;
const options = {
addUtmTracking: addUtmTracking !== false,
};
// Only pass params if they exist
const urlParams = Object.keys(params).length > 0 ? (params as UrlParams) : undefined;
const url = buildCompleteUrl(relativePath, urlParams, options);
return {
content: [
{
type: 'text',
text: `Complete URL with UTM tracking: ${url}`,
},
],
};
}
case 'validate_url_params': {
const params = args as unknown as UrlParams;
const validation = validateUrlParams(params);
return {
content: [
{
type: 'text',
text: validation.valid
? 'Parameters are valid'
: `Validation errors: ${validation.errors.join(', ')}`,
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
});
}
async run(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('URL Builder MCP server running on stdio');
}
}
const server = new UrlBuilderServer();
server.run().catch(console.error);