Skip to main content
Glama
coreymhudson

MCP Sequence Simulation Server

by coreymhudson

evolve_sequence

Simulate evolutionary changes in DNA or protein sequences across generations using mutation, selection, and population dynamics for bioinformatics research.

Instructions

Simulate evolution of sequences over multiple generations

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sequenceYesStarting sequence
generationsYesNumber of generations to simulate
populationSizeYesPopulation size for each generation
mutationRateYesMutation rate per generation (0-1)
selectionPressureNoSelection pressure (0-1), 0 = no selection, 1 = strong selection
fitnessFunctionNoFitness function: 'gc-content', 'length', 'hydrophobic', or 'custom'
targetValueNoTarget value for fitness function (e.g., target GC content)
seedNoRandom seed for reproducible results (optional)
trackLineagesNoTrack individual lineages through generations
outputFormatNoOutput format: 'summary', 'detailed', or 'fasta'

Implementation Reference

  • Main handler function that executes the evolve_sequence tool. Implements evolutionary simulation logic including population initialization, fitness calculation, selection pressure application, mutation, and tracking over multiple generations. Supports both DNA and protein sequences with configurable parameters like mutation rate, selection pressure, fitness functions, and output formats.
    async handler({ 
      sequence, 
      generations,
      populationSize,
      mutationRate,
      selectionPressure = 0,
      fitnessFunction = "gc-content",
      targetValue = 0.5,
      seed,
      trackLineages = false,
      outputFormat = "summary"
    }: { 
      sequence: string;
      generations: number;
      populationSize: number;
      mutationRate: number;
      selectionPressure?: number;
      fitnessFunction?: string;
      targetValue?: number;
      seed?: number;
      trackLineages?: boolean;
      outputFormat?: string;
    }) {
      const generator = new SequenceGenerator(seed);
      const evolutionParams: EvolutionParameters = {
        generations,
        populationSize,
        mutationRate,
        selectionPressure
      };
    
      const isDNA = /^[ATGC]+$/i.test(sequence.replace(/\s/g, ''));
      let population = Array(populationSize).fill(null).map(() => ({
        sequence: sequence.toUpperCase(),
        fitness: 0,
        generation: 0,
        lineageId: Math.random().toString(36).substr(2, 9)
      }));
    
      const evolutionHistory = [];
      const lineageData: Array<{
        generation: number;
        lineageId: string;
        sequence: string;
        fitness: number;
      }> | undefined = trackLineages ? [] : undefined;
    
      for (let gen = 0; gen <= generations; gen++) {
        population.forEach(individual => {
          individual.fitness = calculateFitness(individual.sequence, fitnessFunction, targetValue, isDNA);
        });
    
        population.sort((a, b) => b.fitness - a.fitness);
    
        const genStats = {
          generation: gen,
          populationSize: population.length,
          bestFitness: population[0].fitness,
          worstFitness: population[population.length - 1].fitness,
          averageFitness: population.reduce((sum, ind) => sum + ind.fitness, 0) / population.length,
          bestSequence: population[0].sequence,
          diversity: calculateDiversity(population.map(ind => ind.sequence))
        };
    
        evolutionHistory.push(genStats);
    
        if (trackLineages && lineageData) {
          population.forEach(individual => {
            lineageData.push({
              generation: gen,
              lineageId: individual.lineageId,
              sequence: individual.sequence,
              fitness: individual.fitness
            });
          });
        }
    
        if (gen < generations) {
          const newPopulation = [];
          
          if (selectionPressure > 0) {
            const selectionThreshold = Math.floor(populationSize * (1 - selectionPressure));
            const survivors = population.slice(0, Math.max(2, selectionThreshold));
            
            while (newPopulation.length < populationSize) {
              const parent = survivors[Math.floor(Math.random() * survivors.length)];
              let newSequence = parent.sequence;
              
              if (isDNA) {
                newSequence = generator.mutateDNA(newSequence, { substitutionRate: mutationRate });
              } else {
                newSequence = mutateProtein(newSequence, mutationRate, generator);
              }
              
              newPopulation.push({
                sequence: newSequence,
                fitness: 0,
                generation: gen + 1,
                lineageId: parent.lineageId + '_' + newPopulation.length
              });
            }
          } else {
            population.forEach(individual => {
              let newSequence = individual.sequence;
              
              if (isDNA) {
                newSequence = generator.mutateDNA(newSequence, { substitutionRate: mutationRate });
              } else {
                newSequence = mutateProtein(newSequence, mutationRate, generator);
              }
              
              newPopulation.push({
                sequence: newSequence,
                fitness: 0,
                generation: gen + 1,
                lineageId: individual.lineageId
              });
            });
          }
          
          population = newPopulation;
        }
      }
    
      let output = '';
      if (outputFormat === 'fasta') {
        const finalPop = population.slice(0, Math.min(10, population.length));
        output = finalPop.map((ind, i) => 
          `>evolved_seq_${i + 1}_gen_${generations} fitness=${ind.fitness.toFixed(4)}\n${ind.sequence}`
        ).join('\n\n');
      }
    
      const summary = {
        initialSequence: sequence,
        finalBestSequence: population[0].sequence,
        totalGenerations: generations,
        populationSize,
        mutationRate,
        selectionPressure,
        fitnessFunction,
        targetValue,
        finalBestFitness: population[0].fitness,
        improvementRatio: population[0].fitness / evolutionHistory[0].bestFitness,
        seed: seed || "random"
      };
    
      const result: any = {
        summary,
        evolutionHistory: outputFormat === 'detailed' ? evolutionHistory : evolutionHistory.filter((_, i) => i % Math.max(1, Math.floor(generations / 20)) === 0 || i === generations)
      };
    
      if (outputFormat === 'fasta') {
        result.fastaOutput = output;
      }
    
      if (trackLineages && lineageData) {
        result.lineageData = lineageData;
      }
    
      if (outputFormat === 'detailed') {
        result.finalPopulation = population.slice(0, 10);
      }
    
      return {
        content: [{
          type: "text",
          text: JSON.stringify(result, null, 2)
        }]
      };
    }
  • Tool definition and input schema for evolve_sequence. Defines parameters: sequence (required), generations (required), populationSize (required), mutationRate (required), selectionPressure, fitnessFunction (enum: gc-content, length, hydrophobic, custom), targetValue, seed, trackLineages, and outputFormat (enum: summary, detailed, fasta).
    definition: {
      name: "evolve_sequence",
      description: "Simulate evolution of sequences over multiple generations",
      inputSchema: {
        type: "object",
        properties: {
          sequence: {
            type: "string",
            description: "Starting sequence"
          },
          generations: {
            type: "number",
            description: "Number of generations to simulate",
            minimum: 1
          },
          populationSize: {
            type: "number",
            description: "Population size for each generation",
            minimum: 2
          },
          mutationRate: {
            type: "number",
            description: "Mutation rate per generation (0-1)",
            minimum: 0,
            maximum: 1
          },
          selectionPressure: {
            type: "number",
            description: "Selection pressure (0-1), 0 = no selection, 1 = strong selection",
            minimum: 0,
            maximum: 1
          },
          fitnessFunction: {
            type: "string",
            description: "Fitness function: 'gc-content', 'length', 'hydrophobic', or 'custom'",
            enum: ["gc-content", "length", "hydrophobic", "custom"]
          },
          targetValue: {
            type: "number",
            description: "Target value for fitness function (e.g., target GC content)"
          },
          seed: {
            type: "number",
            description: "Random seed for reproducible results (optional)"
          },
          trackLineages: {
            type: "boolean",
            description: "Track individual lineages through generations",
            default: false
          },
          outputFormat: {
            type: "string",
            description: "Output format: 'summary', 'detailed', or 'fasta'",
            enum: ["summary", "detailed", "fasta"]
          }
        },
        required: ["sequence", "generations", "populationSize", "mutationRate"]
      },
    },
  • src/server.ts:26-37 (registration)
    Registration of evolve_sequence tool in ListToolsRequestSchema handler. The tool definition is exported as part of the tools list available to MCP clients.
    server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          generateDNA.definition,
          generateProtein.definition,
          mutateSequence.definition,
          evolveSequence.definition,
          simulatePhylogeny.definition,
          simulateFastq.definition,
        ],
      };
    });
  • src/server.ts:80-92 (registration)
    Routing of evolve_sequence tool calls in CallToolRequestSchema handler. Maps the tool name 'evolve_sequence' to its handler function with proper type casting of arguments.
    case "evolve_sequence":
      return await evolveSequence.handler(args as {
        sequence: string;
        generations: number;
        populationSize: number;
        mutationRate: number;
        selectionPressure?: number;
        fitnessFunction?: string;
        targetValue?: number;
        seed?: number;
        trackLineages?: boolean;
        outputFormat?: string;
      });
  • Helper functions supporting the evolve_sequence handler: calculateFitness (evaluates sequence fitness based on gc-content, length, or hydrophobicity), calculateDiversity (measures population diversity), and mutateProtein (applies mutations to protein sequences).
    function calculateFitness(sequence: string, fitnessFunction: string, targetValue: number, isDNA: boolean): number {
      switch (fitnessFunction) {
        case 'gc-content':
          if (!isDNA) return 0;
          const gcContent = (sequence.match(/[GC]/gi) || []).length / sequence.length;
          return 1 - Math.abs(gcContent - targetValue);
        
        case 'length':
          return 1 - Math.abs(sequence.length - targetValue) / Math.max(sequence.length, targetValue);
        
        case 'hydrophobic':
          if (isDNA) return 0;
          const hydrophobic = new Set(['A', 'V', 'I', 'L', 'M', 'F', 'Y', 'W']);
          const hydrophobicRatio = sequence.split('').filter(aa => hydrophobic.has(aa)).length / sequence.length;
          return 1 - Math.abs(hydrophobicRatio - targetValue);
        
        default:
          return Math.random();
      }
    }
    
    function calculateDiversity(sequences: string[]): number {
      const uniqueSequences = new Set(sequences);
      return uniqueSequences.size / sequences.length;
    }
    
    function mutateProtein(sequence: string, mutationRate: number, generator: SequenceGenerator): string {
      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() < mutationRate) {
          const currentAA = mutated[i];
          let newAA;
          do {
            newAA = aminoAcids[Math.floor(Math.random() * aminoAcids.length)];
          } while (newAA === currentAA);
          mutated[i] = newAA;
        }
      }
      
      return mutated.join('');
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure but only states the simulation action without detailing traits like computational intensity, output format implications, or side effects. It misses opportunities to explain what 'evolve' entails beyond the basic parameters.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose without unnecessary words. It earns its place by succinctly conveying the tool's function, making it highly concise and well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of 10 parameters, no annotations, and no output schema, the description is incomplete. It fails to address behavioral aspects, output expectations, or usage context, leaving significant gaps for a tool with high parameter count and evolutionary simulation complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100%, so the schema already documents all parameters thoroughly. The description adds no additional meaning beyond the schema, such as explaining interactions between parameters or simulation mechanics, resulting in a baseline score.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('simulate evolution') and resource ('sequences over multiple generations'), making the purpose understandable. However, it doesn't explicitly differentiate from sibling tools like 'mutate_sequence' or 'simulate_phylogeny', which might involve similar evolutionary concepts.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives. The description lacks context about scenarios where evolutionary simulation is appropriate compared to simpler mutation or generation tools, leaving the agent without usage direction.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/coreymhudson/mcp-sequence-simulation'

If you have feedback or need assistance with the MCP directory API, please join our Discord server