Rnv Color Mcp

io.github.RNVizionPublished by GitHub account RNVizionv1.3.0Updated Oct 26, 2026

A complete color workflow over MCP: mix, convert, harmonize, measure, place, and remember palettes.

VerifiedStreamable HTTPWeb executableDeveloper ToolsMedia & Design

Overview

AI-generated overview

Remote MCP server for color work: mix, convert, harmonize, compare and check contrast, plus save and recall named palettes.

What you can do
  • Mix up to 12 colors with weighted blends in rgb, hsv, lab, paint, ryb or cmy modes
  • Convert colors between hex, rgb, hsv, hsl and lab
  • Set lightness while holding hue and chroma, and generate harmonies such as complementary, triadic or analogous
  • Measure perceptual difference (Delta-E) and WCAG contrast with AA/AAA pass-fail
  • Save, list and fetch named palettes that persist across restarts
  • Apply exact case transforms to text
When to use it
Useful when a conversation needs precise color values rather than model-guessed ones: building or adjusting a brand palette, checking text accessibility, deriving light/dark pairs, or producing harmonies from a base color.
Requirements
Nothing to install: it is a hosted remote endpoint connected by URL. The public deployment runs without authentication. Running your own copy needs Python and the listed dependencies; durable palette storage needs HF_TOKEN and a writable Hugging Face Dataset, and scoped auth needs the RNV_AUTH variables.
Before you install
The public endpoint is open, so anyone with the URL can save palettes, and saves on a copy without HF_TOKEN are lost when the container restarts. Saved palettes are written to a Hugging Face Dataset when configured. Palette names and colors you save are stored server-side.

Installation

In SourceWeft

  1. Open Rnv Color Mcp in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Web executable via Streamable HTTP. Remote servers run from the web runtime once configured in a workspace.

Use with an agent

Paste this prompt into Claude Code, Codex, Cursor or another agent — or into SourceWeft chat. The agent reads this server's install guide, shows you its publisher, endpoint and sign-in, and adds it to your client once you agree. Keys never go into a command.

Read https://sourceweft.com/mcp/io.github.RNVizion%2Frnv-color-mcp/install.md and connect the MCP server it describes to my client. Before changing any config, show me its publisher, endpoint and sign-in method and wait for my OK. Never put an API key in a command or in a file I would commit; ask me where it should come from.

Read the install guide the agent follows

Other MCP clients

Add this to your client's mcpServers config.

{
  "mcpServers": {
    "rnv-color": {
      "type": "http",
      "url": "https://rnvizion-rnv-color-mcp.hf.space/mcp"
    }
  }
}

README


title: RNV Color MCP emoji: 🎨 colorFrom: gray colorTo: yellow sdk: docker app_port: 7860 pinned: false short_description: Color workflow MCP server

RNV Color MCP

[rnv-color-mcp MCP server] [Mentioned in Awesome MCP Servers] [tests]

A remote MCP server for a complete color workflow: mix, convert, harmonize, and remember palettes, called in plain language by Claude (or any MCP client), and by anything else that speaks MCP.

Why this exists

The color logic already lived in my desktop suite: a mixer, a palette manager, a picker. Instead of rebuilding it for every new project, I lifted the engine out once and exposed it as a single server. A Claude conversation calls it today; a fashion design app will call the same backend tomorrow. Build the engine once, let both consume it.

Underneath, it's a small thesis about working with LLMs: a model is great at deciding what you want and terrible at exact arithmetic. So the model picks the tool and the intent, and the tool owns the precise values. The server resolves or it refuses; it never guesses a color.

What it does

ToolWhat it does
mix_colorsBlend up to 12 colors. Modes: rgb, hsv, lab (digital) and paint (Kubelka-Munk pigment physics), ryb (artist's wheel), cmy (subtractive).
convert_colorConvert between hex, rgb, hsv, hsl, lab.
place_lightnessHold a color's hue and chroma; set its lightness. One hue at two L* values is a light/dark pair. Refuses out-of-gamut rather than clamping, and reports the 8-bit quantization error.
generate_harmonycomplementary, analogous, triadic, split-complementary, tetradic/square, monochromatic, compound.
color_differencePerceptual difference (Delta-E, CIEDE2000 or CIE76) between two colors.
contrast_checkWCAG contrast ratio plus AA/AAA pass/fail for accessible text.
transform_text11 exact case transforms (UPPERCASE, camelCase, snake_case, …).
save_palette / list_palettes / get_paletteName a palette, recall it later. Persists across restarts.

Every color input accepts a hex (#d2bc93), a CSS name (red), an RNV brand name (brand gold, near-black), or a saved-palette reference (Spring line, or Spring line:2 for its second swatch). Brand names win over CSS names on collision (gold, blue, teal); css:blue forces the universal one.

Connect in 30 seconds

This is a hosted server, so there's nothing to install. In Claude: Settings → Connectors → Add custom connector, then paste:

https://rnvizion-rnv-color-mcp.hf.space/mcp

Leave auth blank, add it, then toggle it on in a chat with the + menu.

Try it

Once connected, just talk:

"Save a palette named Spring line: near-black and brand gold." "Pull my Spring line palette and give me three complementary accents for outerwear." "Mix crimson and royalblue like real pigment."

The first call saves; the second composes get_palette → generate_harmony; the third runs the Kubelka-Munk paint model, so the blend darkens the way mixed pigment actually does, not the way averaged light does.

Authentication

The server implements OAuth 2.1 resource-server authentication with enforced per-tool scopes; the public endpoint runs with it disabled, so connecting by URL works with no setup.

When enabled (RNV_AUTH=1 plus a key source), the server validates bearer tokens against issuer, audience, expiry, and signature; serves RFC 9728 protected resource metadata at /.well-known/oauth-protected-resource/mcp; returns a spec-compliant WWW-Authenticate challenge on 401; and enforces two scopes: read covers every read-only tool, write covers save_palette, the only tool that mutates anything. A token without the write scope does not see save_palette in its tool list at all; out-of-scope tools are hidden rather than refused, so nothing leaks about what exists behind a scope you lack.

Enforcement is covered by the test suite, run in CI on every push (status badge at the top of this file), spanning the token-validation matrix (missing, malformed, wrong issuer, wrong audience, expired, valid) and end-to-end scope enforcement over real HTTP. The suite mints its own keys and requires no credentials. Beyond auth, it pins parts of the engine: the brand vocabulary mirror; the contrast, difference and placement contract; selector and input refusals; color-name resolution; what the mix modes compute; the hsv and hsl conversions; the palette write path; store durability. Harmony and text outputs are exercised by the smoke scripts and are not yet under regression test.

Moving from the self-issued development key to a real identity provider is configuration, not code: point RNV_AUTH_JWKS_URI at the provider's JWKS endpoint and set the issuer and audience to match.

VariablePurpose
RNV_AUTHThe switch. 1 / true / yes / on enables auth; unset means off.
RNV_AUTH_JWKS_URIA provider's JWKS endpoint. Use this or RNV_AUTH_PUBLIC_KEY.
RNV_AUTH_PUBLIC_KEYA static PEM public key, for development against a self-issued keypair.
RNV_AUTH_ISSUERExpected token issuer; also advertised as the authorization server.
RNV_AUTH_AUDIENCEExpected token audience. Bound to the /mcp path.
RNV_AUTH_BASE_URLServer base URL, without /mcp.

With RNV_AUTH on and no key source set, the server refuses to start rather than coming up unprotected.

Run it yourself

bash
pip install -r requirements.txtpython server.py                  # Streamable HTTP on $PORT (default 7860)
pip install -r tests/requirements-dev.txtpython -m pytest                  # the suite; needs no credentialspython tests/server_test.py       # smoke: exercises every tool in-process

Set HF_TOKEN to write palettes through to a private Hugging Face Dataset for durable storage. The suite and both smoke scripts ignore HF_TOKEN and use a throwaway store, so they can never write to a real Dataset.

Running a copy?

The code is MIT-licensed; you're free to rebuild, host, and modify it, and I'd rather you did than didn't. What a license can't carry is the environment.

Some capabilities depend on how a deployment is configured, not on the code alone:

  • Palette persistence needs HF_TOKEN and a writable Dataset. Without it, saves land in the container's local working copy and vanish with it; save_palette returns durable: false with a durable_reason naming the step that stopped it.
  • Scoped authorization needs RNV_AUTH and a configured issuer. Unset, the server runs open — which is correct for a public demo and wrong for anything else.
  • Anything that fetches needs outbound network access.

A rebuild missing one of those usually doesn't fail loudly; it returns success and drops the result. That is worse than an outage, because an outage tells you.

So: if you're running a copy and something behaves oddly, check the environment before you check the code, and check against the canonical deployment before you file anything.

Canonical endpoint: https://rnvizion-rnv-color-mcp.hf.space/mcp Canonical source: https://github.com/RNVizion/rnv-color-mcp

A copy served from another URL may be older, modified, or differently configured. That's fine and allowed; it just isn't this.

The tools resolve or refuse; they don't guess. A copy can only keep that promise if the ground under it was checked first.

Notes

  • Brand colors are mirrored, not owned. The vocabulary lives in engine/brand_vocab.py, mirrored from engine/brand.py in RNVizion/rnv-brand and corrected when drift is detected against it. It is carried locally on purpose: resolve_color is the hot path, and a fetch there would have to answer what happens when it fails — fail closed and the server refuses every color, fall back and the local copy is needed anyway, guess and the promise above is already broken. Identifiers match upstream's since the register retired local names on 2026-08-17; the check compares values. A scheduled job compares the mirror with upstream daily and gates nothing. The register and its reasoning live in BRAND_COLORS.md, in rnv-brand.
  • Engine is dependency-free. The color math, harmony, and text logic are pure standard library, lifted Qt-free from the desktop apps. Only the server layer needs fastmcp.
  • Honest by design. An unknown color name is refused, not guessed. An unverifiable token is refused, with a reason. Same principle at both layers.

Stack

Python · FastMCP (Streamable HTTP) · Hugging Face Spaces (Docker) · huggingface_hub for durable palette storage.


Built by Christian "RNVizion" Smith.

Source: README.md at commit 3c3f7d3

Tools

10

mix_colors

Blend up to 12 colors into one. Each color may be a hex (#d2bc93), a CSS name (red), an RNV brand name (brand gold, near-black), or a saved-palette reference (Spring line, or 'Spring line:2' for its 2nd swatch). Optional integer weights bias the blend (defaults to equal). mode selects the model: rgb/hsv/lab are digital blends (lab is perceptual and the default, best for on-screen color); paint mixes pigments via Kubelka-Munk physics (colors darken like real paint, use it for physical-media matching); ryb is the artist's color wheel; cmy is subtractive like printer inks. Returns hex and rgb. Reach for paint when BLENDING brand colors into a new one, and lab when SAMPLING between them: lab returns the point between its ingredients, which is right for finding a midpoint; paint models mixing them as pigments, which makes a different color. RNV's #00b0a0 was blended in paint; the same ingredients at the same weights in lab give #9cc1a5, 15.94 away and far closer to the brand gold it exists to be distinguishable from. paint and cmy work per RGB channel, so an ingredient with an empty channel (pure digital primaries such as #0000ff or #ffff00) dominates that channel: at equal weights yellow + css:blue comes out black in paint and near-black (#070707) in cmy, where ryb gives green. Weight shifts this in cmy and hardly in paint: white + css:blue at 10:1 is #8484ff in cmy and #0202fe in paint. For pigment-like results use pigment-like colors, or ryb for idealized primaries. Brand names win over CSS names on collision: 'blue' is RNV blue, css:blue is #0000ff. Read-only and deterministic: it computes a result and stores nothing, so it is safe to call repeatedly with no side effects. Use to combine multiple colors into a single blend; to convert one color between formats use convert_color, and to measure how far apart two colors are use color_difference.

Parametersmodecolorsrequiredweights

convert_color

Convert a color between formats. Input accepts a hex, CSS name, RNV brand name, or saved-palette reference. With `to` set to one of hex/rgb/hsv/hsl/lab, returns just that format; leave `to` out to get all of them. hsv and hsl are fractions from 0 to 1, hue included (multiply hue by 360 for degrees). Read-only and deterministic, with no side effects. Use for format conversion of a single color; to blend several colors into one use mix_colors, and to compare two colors use color_difference.

Parameterstocolorrequired

place_lightness

Hold a color's hue and chroma and set its lightness, returning one result per requested L*. color accepts a hex, CSS name, RNV brand name, or saved-palette reference; lightness is a list of L* values in 0-100. This is the operation behind a LIGHT/DARK PAIR: one hue at two lightnesses, with a and b held exactly, which is how a color that works on a dark ground gets a partner that works on a light one. Use it when you need the same color at a different lightness; use mix_colors when you need a different color. Returns the source color's LAB, and for each placement the hex, the achieved LAB, and the quantization error per axis -- 8-bit hex cannot store a and b to the precision LAB expresses, so two placements of one hue differ by a few hundredths on both axes however exactly the input held them. That is the storage format, not the operation, and it is reported rather than hidden. Read-only and deterministic. Refuses rather than clamps: not every (L*, a, b) exists in sRGB, and a lightness too far from a chromatic color's range is rejected with the reason instead of being silently moved to the nearest color that fits.

Parameterscolorrequiredlightnessrequired

generate_harmony

Generate a color harmony from a base color. base accepts a hex, CSS name, RNV brand name, or saved-palette reference (e.g. 'Spring line:2'). scheme is one of: complementary, analogous, triadic, split-complementary, tetradic (a.k.a. square), monochromatic, compound. Returns a list of hex colors. Read-only and deterministic: it derives the colors from the base and stores nothing, so it has no side effects and is safe to call repeatedly. Use to expand one base color into a related set; to blend existing colors into a single color use mix_colors, and to persist a set you like use save_palette.

Parametersbaserequiredschemerequired

color_difference

Perceptual difference (Delta-E) between two colors. color1 and color2 accept a hex, CSS name, RNV brand name, or saved-palette reference. method is 'ciede2000' (default, modern standard) or 'cie76'. A value near 1.0 is the threshold the eye can just notice; larger means more different. Returns the value and a plain-language interpretation. Read-only and deterministic, with no side effects. Use ciede2000 for accuracy and pick cie76 only to match a legacy system; to test whether text is legible on a background (not raw difference) use contrast_check instead.

Parameterscolor1requiredcolor2requiredmethod

contrast_check

WCAG contrast ratio between a foreground and background color, for accessibility. Both accept a hex, CSS name, RNV brand name, or saved-palette reference. Returns the ratio (1.0-21.0) plus pass/fail for AA and AAA at normal and large text sizes and for UI components. Read-only and deterministic, with no side effects. Use this for legibility and accessibility checks; to measure raw perceptual difference between two colors rather than readability use color_difference instead.

Parametersbackgroundrequiredforegroundrequired

transform_text

Apply an exact, deterministic text transformation. operation is one of: UPPERCASE, lowercase, 'Title Case', 'Sentence case', camelCase, PascalCase, snake_case, CONSTANT_CASE, kebab-case, dot.case, 'iNVERTED cASE'. Read-only and deterministic: it returns the transformed string and changes nothing, safe to call repeatedly. Use whenever exact, reproducible case formatting matters rather than rewriting the text by hand or guessing the casing.

Parameterstextrequiredoperationrequired

save_palette

Persist a named color palette for later retrieval with get_palette or list_palettes. Each entry in colors accepts what every other tool accepts: a hex (#d2bc93), a CSS name (red), an RNV brand name (brand gold), or a saved-palette reference ('Spring line:2'); each is resolved and stored as normalized hex, and an unknown token refuses the whole save naming its position. Optional notes are stored as the palette's description. Author is recorded as RNVizion. The name is refused, with the reason, if it is an RNV brand name, a CSS color name, a 'css:' form, a hex literal, or contains ':' -- a palette resolves ahead of brand and CSS names, so such a name would redefine a color for every caller. This WRITES to the palette store and is the only tool here that does. Reusing an existing name overwrites that palette: save and update are the same call (an upsert), there is no separate update operation. Returns a `durable` flag: true if the palette reached durable storage (the HF Dataset) and will survive a restart, false if it saved to the local working copy only (which is lost on rebuild), with `durable_reason` naming the step that stopped it. Use when the user wants to keep a set of colors under a name for reuse across sessions, such as a brand or launch palette; to read a palette back use get_palette, and to see what already exists use list_palettes. The saved name can then be passed to mix_colors, convert_color, and generate_harmony as a palette reference.

Parametersnamerequirednotescolorsrequired

list_palettes

List every saved palette as name + colors. Read-only; no side effects. Use to discover what palettes exist or to find a name before calling get_palette; to fetch one palette's full detail use get_palette, and to create or overwrite one use save_palette.

Takes no parameters.

get_palette

Retrieve one saved palette by name, returning its colors and metadata. Returns null if no palette by that name exists. Read-only; no side effects. Use when you already know the palette name; to list available names first use list_palettes, and to create or update a palette use save_palette.

Parametersnamerequired

Version history

2
  1. v1.3.0LatestSep 28, 2026
  2. v1.2.0Sep 16, 2026

Score

100% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 daysRequired25 pts
  • Auth declaredRequired10 pts
  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts