Skip to main content
Glama
simen

VICE C64 Emulator MCP Server

by simen

readColorRam

Reads color RAM to retrieve foreground color values for each character on the Commodore 64 screen. Returns a 25x40 grid of color data with names and usage summary for debugging C64 programs.

Instructions

Read color RAM ($D800-$DBE7) and return color values with names.

Color RAM determines the foreground color of each character on screen.

Returns:

  • 25x40 grid of color values (0-15) with names

  • Summary of colors used

Related tools: readScreen, readVicState

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
summaryNoReturn only color usage summary, not full grid (default: false)

Implementation Reference

  • Handler function that reads color RAM memory from $D800 for 1000 bytes, extracts 4-bit color values, computes usage statistics, optionally builds a 25x40 color grid, and formats the response using getColorInfo for color names.
    async (args) => {
      try {
        // Color RAM is always at $D800
        const colorData = await client.readMemory(0xd800, 0xd800 + 999);
    
        // Count color usage
        const colorCounts = new Map<number, number>();
        for (const byte of colorData) {
          const color = byte & 0x0f;
          colorCounts.set(color, (colorCounts.get(color) || 0) + 1);
        }
    
        // Sort by frequency
        const colorUsage = Array.from(colorCounts.entries())
          .sort((a, b) => b[1] - a[1])
          .map(([color, count]) => ({
            color: getColorInfo(color),
            count,
            percentage: Math.round((count / 1000) * 100),
          }));
    
        const response: Record<string, unknown> = {
          address: { value: 0xd800, hex: "$D800" },
          summary: {
            uniqueColors: colorUsage.length,
            dominantColor: colorUsage[0]?.color || null,
            usage: colorUsage,
          },
        };
    
        if (!args.summary) {
          // Convert to 25 lines of 40 color values
          const colorLines: Array<Array<{ value: number; name: string }>> = [];
          for (let row = 0; row < 25; row++) {
            const line: Array<{ value: number; name: string }> = [];
            for (let col = 0; col < 40; col++) {
              const offset = row * 40 + col;
              line.push(getColorInfo(colorData[offset]));
            }
            colorLines.push(line);
          }
          response.grid = colorLines;
        }
    
        response.hint =
          colorUsage.length === 1
            ? `Entire screen uses ${colorUsage[0].color.name} (${colorUsage[0].color.value})`
            : `${colorUsage.length} colors used. Dominant: ${colorUsage[0]?.color.name} (${colorUsage[0]?.percentage}%)`;
    
        return formatResponse(response);
      } catch (error) {
        return formatError(error as ViceError);
      }
    }
  • Input schema defining optional 'summary' boolean parameter to control whether full color grid or just summary is returned.
    inputSchema: z.object({
      summary: z
        .boolean()
        .optional()
        .describe("Return only color usage summary, not full grid (default: false)"),
    }),
  • src/index.ts:1164-1237 (registration)
    MCP tool registration for 'readColorRam', including description, input schema, and handler reference.
    server.registerTool(
      "readColorRam",
      {
        description: `Read color RAM ($D800-$DBE7) and return color values with names.
    
    Color RAM determines the foreground color of each character on screen.
    
    Returns:
    - 25x40 grid of color values (0-15) with names
    - Summary of colors used
    
    Related tools: readScreen, readVicState`,
        inputSchema: z.object({
          summary: z
            .boolean()
            .optional()
            .describe("Return only color usage summary, not full grid (default: false)"),
        }),
      },
      async (args) => {
        try {
          // Color RAM is always at $D800
          const colorData = await client.readMemory(0xd800, 0xd800 + 999);
    
          // Count color usage
          const colorCounts = new Map<number, number>();
          for (const byte of colorData) {
            const color = byte & 0x0f;
            colorCounts.set(color, (colorCounts.get(color) || 0) + 1);
          }
    
          // Sort by frequency
          const colorUsage = Array.from(colorCounts.entries())
            .sort((a, b) => b[1] - a[1])
            .map(([color, count]) => ({
              color: getColorInfo(color),
              count,
              percentage: Math.round((count / 1000) * 100),
            }));
    
          const response: Record<string, unknown> = {
            address: { value: 0xd800, hex: "$D800" },
            summary: {
              uniqueColors: colorUsage.length,
              dominantColor: colorUsage[0]?.color || null,
              usage: colorUsage,
            },
          };
    
          if (!args.summary) {
            // Convert to 25 lines of 40 color values
            const colorLines: Array<Array<{ value: number; name: string }>> = [];
            for (let row = 0; row < 25; row++) {
              const line: Array<{ value: number; name: string }> = [];
              for (let col = 0; col < 40; col++) {
                const offset = row * 40 + col;
                line.push(getColorInfo(colorData[offset]));
              }
              colorLines.push(line);
            }
            response.grid = colorLines;
          }
    
          response.hint =
            colorUsage.length === 1
              ? `Entire screen uses ${colorUsage[0].color.name} (${colorUsage[0].color.value})`
              : `${colorUsage.length} colors used. Dominant: ${colorUsage[0]?.color.name} (${colorUsage[0]?.percentage}%)`;
    
          return formatResponse(response);
        } catch (error) {
          return formatError(error as ViceError);
        }
      }
    );
  • Color palette constants and helper functions getColorName/getColorInfo used to interpret raw color RAM bytes into named C64 colors (0-15). Called multiple times in the handler.
    export const C64_COLORS = [
      "black",
      "white",
      "red",
      "cyan",
      "purple",
      "green",
      "blue",
      "yellow",
      "orange",
      "brown",
      "light red",
      "dark gray",
      "gray",
      "light green",
      "light blue",
      "light gray",
    ] as const;
    
    export type C64Color = (typeof C64_COLORS)[number];
    
    export function getColorName(value: number): C64Color {
      return C64_COLORS[value & 0x0f];
    }
    
    export function getColorInfo(value: number): { value: number; name: C64Color } {
      return {
        value: value & 0x0f,
        name: getColorName(value),
      };
    }
Behavior4/5

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

With no annotations provided, the description carries full burden and does well by specifying the exact memory range, output format (25x40 grid with color names), and optional summary behavior. It clearly indicates this is a read-only operation and describes what information will be returned, though it doesn't mention potential limitations like performance impact or memory access constraints.

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 efficiently structured with clear sections: purpose statement, functional explanation, return format details, and related tools. Every sentence adds value without redundancy, and key information is front-loaded. The four sentences each serve distinct purposes with zero waste.

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

Completeness4/5

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

For a read operation with no annotations, 100% schema coverage, and no output schema, the description provides excellent coverage of purpose, behavior, and parameters. It explains what Color RAM is, what the tool reads, and what it returns. The main gap is lack of explicit output schema documentation, but the description compensates well by detailing the return format.

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

Parameters4/5

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

The schema has 100% description coverage for its single parameter, so the baseline is 3. The description adds value by explaining the default behavior ('default: false') and clarifying that when summary=true, it returns 'only color usage summary, not full grid,' which provides important semantic context beyond the schema's boolean type.

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

Purpose5/5

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

The description clearly states the specific action ('Read color RAM'), the resource location ('$D800-$DBE7'), and the output format ('return color values with names'). It distinguishes from sibling tools by mentioning related tools readScreen and readVicState, indicating this is specifically for color data rather than screen content or VIC chip state.

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

Usage Guidelines4/5

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

The description provides clear context by explaining what Color RAM does ('determines the foreground color of each character on screen') and lists related tools, giving implicit guidance on when this tool is appropriate. However, it doesn't explicitly state when to use this versus alternatives like readScreen or readVicState, nor does it provide exclusion criteria.

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/simen/vice-mcp'

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