import { z } from 'zod'
import { diffLines } from 'diff'
// ============================================================================
// TYPES
// ============================================================================
export type ToolArgs = Record<string, unknown>
export type PipelineStage = (input: any) => any
export type ToolDefinition = {
pipeline: ReadonlyArray<PipelineStage>
description: string
inputSchema: object
}
export type DiffRequest = {
text1: string
text2: string
ignoreWhitespace?: boolean
ignoreCase?: boolean
ignoreLineEndings?: boolean
includeFormattedOutput?: boolean
}
export type DiffResult = {
identical: boolean
differences?: Array<{
type: 'add' | 'remove' | 'equal'
value: string
lineNumber: number
}>
summary: string
statistics: {
totalLines: number
addedLines: number
removedLines: number
changedLines: number
totalCharacters: number
addedCharacters: number
removedCharacters: number
similarityPercentage: number
}
formattedDiff?: string
}
// ============================================================================
// PIPELINE UTILITIES
// ============================================================================
export const runPipeline = (stages: ReadonlyArray<PipelineStage>) =>
(input: any): any => stages.reduce((acc, stage) => stage(acc), input)
// ============================================================================
// PROCESSING STAGES
// ============================================================================
export const validateDiffInputs = (args: ToolArgs): DiffRequest => {
const schema = z.object({
text1: z.string(),
text2: z.string(),
ignoreWhitespace: z.boolean().optional(),
ignoreCase: z.boolean().optional(),
ignoreLineEndings: z.boolean().optional(),
includeFormattedOutput: z.boolean().optional()
})
return schema.parse(args)
}
export const normalizeTexts = (request: DiffRequest): DiffRequest => {
const normalizeText = (text: string): string => {
let result = text
if (request.ignoreLineEndings) {
result = result.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
}
if (request.ignoreCase) {
result = result.toLowerCase()
}
if (request.ignoreWhitespace) {
result = result.replace(/\s+/g, ' ').trim()
}
return result
}
return {
...request,
text1: normalizeText(request.text1),
text2: normalizeText(request.text2)
}
}
export const compareTexts = (request: DiffRequest) => ({
identical: request.text1 === request.text2
})
export const calculateDiff = (request: DiffRequest): DiffResult => {
const { text1, text2 } = request
const identical = text1 === text2
if (identical) {
return {
identical: true,
summary: 'Texts are identical',
statistics: {
totalLines: text1.split('\n').length,
addedLines: 0,
removedLines: 0,
changedLines: 0,
totalCharacters: text1.length,
addedCharacters: 0,
removedCharacters: 0,
similarityPercentage: 100
}
}
}
const rawDiff = diffLines(text1, text2, { newlineIsToken: false })
// Build differences with proper line tracking
const changeTypes = {
added: 'add',
removed: 'remove',
equal: 'equal'
} as const
let lineNumber = 1
const differences = []
for (const change of rawDiff) {
const type = change.added ? changeTypes.added : change.removed ? changeTypes.removed : changeTypes.equal
differences.push({
type,
value: change.value,
lineNumber
})
// Only increment line number for non-removed changes
if (!change.removed) {
lineNumber += (change.count || 1)
}
}
// Calculate statistics
const addedLines = differences.filter(d => d.type === 'add').length
const removedLines = differences.filter(d => d.type === 'remove').length
const addedChars = differences
.filter(d => d.type === 'add')
.reduce((sum, d) => sum + d.value.length, 0)
const removedChars = differences
.filter(d => d.type === 'remove')
.reduce((sum, d) => sum + d.value.length, 0)
const totalLines = Math.max(text1.split('\n').length, text2.split('\n').length)
const unchangedLines = totalLines - Math.max(addedLines, removedLines)
const similarity = totalLines > 0 ? Math.round((unchangedLines / totalLines) * 100) : 100
return {
identical: false,
differences,
summary: `Texts differ: ${addedLines} lines added, ${removedLines} lines removed (${similarity}% similar)`,
statistics: {
totalLines,
addedLines,
removedLines,
changedLines: addedLines + removedLines,
totalCharacters: Math.max(text1.length, text2.length),
addedCharacters: addedChars,
removedCharacters: removedChars,
similarityPercentage: similarity
}
}
}
export const addFormattedOutput = (result: DiffResult): DiffResult => {
if (result.identical || !result.differences) {
return result
}
const formatLine = (diff: { type: string; value: string }) => {
const prefix = diff.type === 'add' ? '+ ' : diff.type === 'remove' ? '- ' : ' '
return diff.value
.split('\n')
.filter(line => line !== '')
.map(line => `${prefix}${line}`)
.join('\n')
}
const formatted = result.differences
.map(formatLine)
.filter(line => line.trim())
.join('\n')
return {
...result,
formattedDiff: `\`\`\`diff\n${formatted}\n\`\`\``
}
}
export const conditionallyAddFormattedOutput = (result: DiffResult): DiffResult => {
// Check if the original request wanted formatted output
const request = result as any // We know this carries the original request data
if (request.includeFormattedOutput !== false) { // Default to true
return addFormattedOutput(result)
}
return result
}
// ============================================================================
// TOOL DEFINITIONS
// ============================================================================
export const toolDefinitions = {
compare_texts: {
pipeline: [
validateDiffInputs,
normalizeTexts,
compareTexts
],
description: 'Compare two text strings and determine if they are identical',
inputSchema: {
type: 'object',
properties: {
text1: { type: 'string', description: 'First text to compare' },
text2: { type: 'string', description: 'Second text to compare' }
},
required: ['text1', 'text2']
}
},
get_detailed_diff: {
pipeline: [
validateDiffInputs,
normalizeTexts,
calculateDiff,
conditionallyAddFormattedOutput
],
description: 'Get detailed differences with statistics and formatted output',
inputSchema: {
type: 'object',
properties: {
text1: { type: 'string', description: 'First text to compare' },
text2: { type: 'string', description: 'Second text to compare' },
ignoreWhitespace: { type: 'boolean', description: 'Ignore whitespace differences' },
ignoreCase: { type: 'boolean', description: 'Ignore case differences' },
ignoreLineEndings: { type: 'boolean', description: 'Ignore line ending differences' },
includeFormattedOutput: { type: 'boolean', description: 'Include formatted diff output', default: true }
},
required: ['text1', 'text2']
}
}
} as const
// ============================================================================
// TYPE-SAFE DERIVATIONS
// ============================================================================
export type ToolName = keyof typeof toolDefinitions
export const tools: Record<ToolName, (args: ToolArgs) => DiffResult> = Object.fromEntries(
Object.entries(toolDefinitions).map(([name, def]) => [
name,
runPipeline(def.pipeline)
])
) as Record<ToolName, (args: ToolArgs) => DiffResult>
export const getToolsMetadata = () => ({
tools: Object.entries(toolDefinitions).map(([name, def]) => ({
name,
description: def.description,
inputSchema: def.inputSchema
}))
})
export const isValidToolName = (name: string): name is ToolName => {
return name in toolDefinitions
}
// ============================================================================
// REQUEST PROCESSING
// ============================================================================
export const processRequest = (request: unknown) => {
// Parse MCP request
const mcpRequest = z.object({
method: z.string(),
params: z.unknown().optional()
}).parse(request)
// Handle tools/list
if (mcpRequest.method === 'tools/list') {
return getToolsMetadata()
}
// Handle tools/call
if (mcpRequest.method === 'tools/call') {
const { name, arguments: args } = z.object({
name: z.string(),
arguments: z.record(z.unknown()).default({})
}).parse(mcpRequest.params)
// Type-safe tool name validation
if (!isValidToolName(name)) {
throw new Error(`Unknown tool: ${name}`)
}
// Execute pipeline with full type safety
return tools[name](args)
}
throw new Error(`Unsupported method: ${mcpRequest.method}`)
}
export const formatResponse = (result: unknown) => ({
content: [{
type: 'text' as const,
text: JSON.stringify(result, null, 2)
}]
})