import { z } from "zod";
import { SequenceGenerator, MutationParameters } from "../utils/sequenceUtils.js";
export const mutateSequence = {
definition: {
name: "mutate_sequence",
description: "Apply mutations to DNA or protein sequences",
inputSchema: {
type: "object",
properties: {
sequence: {
type: "string",
description: "Input sequence to mutate"
},
sequenceType: {
type: "string",
description: "Type of sequence: 'dna' or 'protein'",
enum: ["dna", "protein"]
},
substitutionRate: {
type: "number",
description: "Substitution mutation rate (0-1), default 0.01",
minimum: 0,
maximum: 1
},
insertionRate: {
type: "number",
description: "Insertion mutation rate (0-1), default 0.001",
minimum: 0,
maximum: 1
},
deletionRate: {
type: "number",
description: "Deletion mutation rate (0-1), default 0.001",
minimum: 0,
maximum: 1
},
transitionBias: {
type: "number",
description: "Transition vs transversion bias for DNA (default 2.0)",
minimum: 0
},
iterations: {
type: "number",
description: "Number of mutation iterations, default 1",
minimum: 1
},
seed: {
type: "number",
description: "Random seed for reproducible results (optional)"
},
outputFormat: {
type: "string",
description: "Output format: 'fasta' or 'plain'",
enum: ["fasta", "plain"]
}
},
required: ["sequence", "sequenceType"]
},
},
async handler({
sequence,
sequenceType,
substitutionRate = 0.01,
insertionRate = 0.001,
deletionRate = 0.001,
transitionBias = 2.0,
iterations = 1,
seed,
outputFormat = "fasta"
}: {
sequence: string;
sequenceType: string;
substitutionRate?: number;
insertionRate?: number;
deletionRate?: number;
transitionBias?: number;
iterations?: number;
seed?: number;
outputFormat?: string;
}) {
const generator = new SequenceGenerator(seed);
const mutationParams: MutationParameters = {
substitutionRate,
insertionRate,
deletionRate,
transitionBias
};
const results = [];
let currentSequence = sequence.toUpperCase();
results.push({
iteration: 0,
sequence: currentSequence,
length: currentSequence.length,
changes: []
});
for (let i = 1; i <= iterations; i++) {
const previousSequence = currentSequence;
if (sequenceType === 'dna') {
currentSequence = generator.mutateDNA(currentSequence, mutationParams);
} else {
currentSequence = mutateProtein(currentSequence, mutationParams, generator);
}
const changes = findChanges(previousSequence, currentSequence);
results.push({
iteration: i,
sequence: currentSequence,
length: currentSequence.length,
changes: changes,
changeCount: changes.length
});
}
let output = '';
if (outputFormat === 'fasta') {
output = results.map(result =>
`>mutated_${sequenceType}_iter_${result.iteration} length=${result.length} changes=${result.changeCount || 0}\n${result.sequence}`
).join('\n\n');
} else {
output = results.map(result => result.sequence).join('\n');
}
const finalSequence = results[results.length - 1];
const totalChanges = results.slice(1).reduce((sum, r) => sum + (r.changeCount || 0), 0);
const stats = {
originalLength: sequence.length,
finalLength: finalSequence.length,
totalIterations: iterations,
totalChanges,
mutationRates: mutationParams,
sequenceType,
seed: seed || "random"
};
return {
content: [{
type: "text",
text: JSON.stringify({
statistics: stats,
mutations: results,
sequences: outputFormat === 'fasta' ? output : undefined,
rawOutput: outputFormat === 'plain' ? output : undefined
}, null, 2)
}]
};
}
};
function mutateProtein(sequence: string, params: MutationParameters, generator: SequenceGenerator): string {
const { substitutionRate = 0.01, insertionRate = 0.001, deletionRate = 0.001 } = params;
const aminoAcids = ['A', 'R', 'N', 'D', 'C', 'Q', 'E', 'G', 'H', 'I', 'L', 'K', 'M', 'F', 'P', 'S', 'T', 'W', 'Y', 'V'];
let mutated = sequence.split('');
for (let i = 0; i < mutated.length; i++) {
if (Math.random() < substitutionRate) {
const currentAA = mutated[i];
let newAA;
do {
newAA = aminoAcids[Math.floor(Math.random() * aminoAcids.length)];
} while (newAA === currentAA);
mutated[i] = newAA;
}
if (Math.random() < insertionRate) {
const randomAA = aminoAcids[Math.floor(Math.random() * aminoAcids.length)];
mutated.splice(i, 0, randomAA);
i++;
}
if (Math.random() < deletionRate && mutated.length > 1) {
mutated.splice(i, 1);
i--;
}
}
return mutated.join('');
}
function findChanges(original: string, mutated: string): Array<{
type: 'substitution' | 'insertion' | 'deletion';
position: number;
original?: string;
mutated?: string;
}> {
const changes: Array<{
type: 'substitution' | 'insertion' | 'deletion';
position: number;
original?: string;
mutated?: string;
}> = [];
let i = 0, j = 0;
while (i < original.length || j < mutated.length) {
if (i >= original.length) {
changes.push({
type: 'insertion',
position: j,
mutated: mutated[j]
});
j++;
} else if (j >= mutated.length) {
changes.push({
type: 'deletion',
position: i,
original: original[i]
});
i++;
} else if (original[i] !== mutated[j]) {
if (i + 1 < original.length && original[i + 1] === mutated[j]) {
changes.push({
type: 'deletion',
position: i,
original: original[i]
});
i++;
} else if (j + 1 < mutated.length && original[i] === mutated[j + 1]) {
changes.push({
type: 'insertion',
position: j,
mutated: mutated[j]
});
j++;
} else {
changes.push({
type: 'substitution',
position: i,
original: original[i],
mutated: mutated[j]
});
i++;
j++;
}
} else {
i++;
j++;
}
}
return changes;
}