Skip to main content
Glama

comfyui-character-mcp

An MCP server that exposes ComfyUI-generated character "avatars" as tools. Each character is a preset: a frozen ComfyUI workflow graph plus a small, named set of controls (pose, expression, seed, ...) that a caller is allowed to adjust. The model never sees or edits the ComfyUI graph itself - only the specific knobs each preset chooses to expose.

Why stdio, not HTTP

This server runs as a local subprocess that the MCP client (Claude Desktop / Claude Code) launches directly, talking JSON-RPC over stdin/stdout. That's the natural transport when ComfyUI and the MCP server both run on the same machine, since there's no networking or auth to set up. Returned images are sent as base64-encoded content blocks regardless of transport - stdio doesn't limit what can flow back to the client, it only changes how the process is reached. If ComfyUI ever needs to live on a different machine, or you want one long-running server shared by multiple clients, this can move to Streamable HTTP transport later without changing the tool logic at all.

Related MCP server: ComfyUI MCP Hub

Requirements

  • A running local ComfyUI instance (default: http://127.0.0.1:8188, override with the COMFYUI_URL environment variable)

  • uv for dependency management

Running

uv run comfyui-character-mcp

To register it with an MCP client, point the client's config at this command (adjust the path to wherever you cloned this repo):

{
  "mcpServers": {
    "comfyui-character": {
      "command": "uv",
      "args": ["run", "--directory", "C:/Users/alexg/src/comfyui-character-mcp", "comfyui-character-mcp"]
    }
  }
}

Tools

  • list_characters() - lists every loaded character, its description, and the expressions (emoji + label) it supports.

  • set_expression(emoji, character?) - renders the avatar showing that expression and returns the image. character is optional; omit it to reuse the currently-selected character. Only emoji in the character's allow-list are accepted - anything else is rejected before it reaches the model.

  • get_current_look() - reports the currently-selected character and its last expression (the small bit of session state the setter tools maintain).

How a render works

Each character is an img2img flow: the preset's reference image is uploaded to ComfyUI, VAE-encoded, and run through the sampler at a low denoise. Low denoise keeps the generated face locked to the reference (that's the identity mechanism); the chosen expression's prompt fragments nudge the face within that budget. Turn denoise up in the preset for more expression range, down to stay closer to the reference. If denoise alone can't hold identity across strong expressions, the next step is adding low-strength ControlNet edge-following.

This works well for expressions that reshape features already present in the reference (a frown, wider eyes, a smirk). It does not work for "additive" expressions that introduce geometry the reference doesn't have at all - cat ears/whiskers, sunglasses, a party hat. No amount of denoise fixes this: the base identity prompt (e.g. "portrait of a cheerful man...") keeps winning even near denoise 1.0, because the prompt and the reference agree on "this is a specific human face" and fight any addition to it. See vocabularies/expressions.json's _additive_emoji_limitation note. The fix isn't a bigger denoise number, it's giving those expressions their own reference image (a version of the character that already has cat ears, say) so the prompt and reference agree on what's being rendered - not yet supported, but the natural next step for this class of expression.

Before encoding, the reference image is also rescaled (via an ImageScale node) so its shorter side is ~512px, preserving aspect ratio - by default, computed automatically from the reference image's own dimensions (see output_width/output_height below). This is a quality setting, not a display one: SD1.5-family checkpoints are trained around 512px, and encoding either much smaller or much larger than that tends to hurt output quality regardless of denoise. Shrinking the image for chat display is a separate, later concern - see "Suggested system prompt" below for controlling that with plain markdown, independent of the actual render resolution.

Expression vocabulary

vocabularies/expressions.json is the shared, character-independent set of emoji. Each maps to {positive, negative} prompt fragments (CLIP can't read emoji, so this translation is mandatory). A preset overrides only the emoji it wants to tune via expression_overrides; everything else falls back to the shared default. The emoji keys are the allow-list set_expression() enforces.

Suggested system prompt

Some MCP clients bridge to models over a text-only tool-result API (common for local models served via Ollama/vLLM-style endpoints). Those clients receive the image content block from set_expression, save it to disk themselves, and hand the model back a text placeholder with a markdown image link to include in its reply - the model never "sees" the pixels, it just has to remember to echo that markdown back to the user. Models reliably forget this unless told to.

Beyond just remembering to include the image, plain markdown image syntax also forces a line break before and after the image, at full render resolution (~512px, kept large for generation quality - see "How a render works" above) - the picture ends up as a big block, disconnected from the model's commentary about it. A raw HTML <img> tag with a width fixes both problems at once: it shrinks the image for display and lets text wrap beside it (most chat renderers that support markdown also render inline HTML):

Don't do this (forces the image onto its own block, no wrap):

![Image](./image-1783113534781.png)

He grinned, clearly pleased with how that turned out.
Do this instead (text flows beside a small inline image):

<img src="./image-1783113534781.png" width="150" align="left"> He grinned,
clearly pleased with how that turned out, and asked what you wanted to see
next.

A system prompt covering all of this:

You have access to an avatar tool for this character. Guidelines:

- Call set_expression(emoji) whenever the character's emotional tone
  shifts in the conversation - don't wait to be asked for a picture.
- Only use emoji from the character's supported list (call
  list_characters() if you're unsure which ones are allowed). Using an
  unsupported emoji will be rejected.
- The tool result includes an image file reference. You MUST include that
  image in your reply - it will not be shown to the user unless you
  include it yourself.
- Wrap the image inline with your commentary instead of putting it on its
  own line: use `<img src="..." width="150" align="left">` (or
  `align="right"`) directly before the paragraph of text that comments on
  the expression, so the picture and the reaction read together. Don't use
  bare `![Image](...)` markdown for this - it breaks the image onto its
  own block and separates it from the text about it.
- Don't describe the image in words instead of showing it; the point of
  the tool is the picture, not a caption.

The middle two points matter for any client; the last two are the ones that fix "generated the image but never showed it" and "showed it, but as an ugly disconnected block" respectively.

Adding a character preset

Each character is a directory under presets/ named after its id, containing three standard-named files - presets/<id>/{preset,workflow}.json,reference.png:

  1. workflow.json - a ComfyUI img2img workflow exported in API format (enable Dev Mode in ComfyUI settings, then "Save (API Format)"). This is the frozen graph; only the inputs named in bindings change per request.

  2. preset.json - the character definition (no id field - the directory name is the id, so there's nothing to keep in sync):

    • base_positive / base_negative - the character's identity prompt and quality negatives. Expression fragments are appended to these.

    • denoise - the identity-vs-expression trade-off knob (not model-facing).

    • output_width / output_height (optional) - the resolution the reference image is rescaled to before VAE encoding, via a resize node in the workflow. If omitted, both are computed automatically from the reference image's own dimensions so its shorter side lands at 512px, preserving aspect ratio - only set these explicitly to override that. Needs matching output_width/output_height bindings pointed at the resize node.

    • bindings - maps logical roles (positive, negative, denoise, seed, reference_image, plus optionally output_width/ output_height) to [node_id, input_name] pairs in the workflow. This is the only place ComfyUI node ids appear.

    • expression_overrides (optional) - per-emoji fragment overrides.

  3. reference.png - the reference portrait this character is generated from.

See presets/example/ for a worked example - before real use, swap reference.png, set ckpt_name in workflow.json to a checkpoint you have installed, and rewrite base_positive for your character.

Available Tools

3 tools
get_current_lookA

Report which character is currently selected and its last expression.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.9/5.0
Behavior2/5

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

No annotations provided; description only states a read operation. Does not disclose any behavioral details (e.g., prerequisites like character must be selected, side effects, or error states). Minimal disclosure beyond purpose.

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?

Single sentence with no superfluous words. Front-loaded with verb and result. Every word earns its place.

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

Completeness3/5

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

No output schema, so description must imply return format. It states 'report' but doesn't specify structure (e.g., string, object). Also lacks context on prerequisites (e.g., requires a character to be selected). Adequate but not fully complete.

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?

No parameters, so schema coverage is trivially 100%. Description adds no parameter info, but none is needed. Baseline for zero parameters is 4.

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?

Description clearly states it reports the currently selected character and its last expression, distinguishing it from sibling tools list_characters (lists all characters) and set_expression (modifies expression).

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?

Usage is implicitly clear: use when you need the current selection/expression. No explicit when/why-not guidance, but the single purpose and zero parameters make it straightforward.

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

list_charactersA

List available avatar characters and the expressions each one supports.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior2/5

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

No annotations are provided, so the description must disclose behavioral traits. It only states it lists characters and expressions, but does not mention that it is a read-only operation, any authentication requirements, or the format/ordering of results. The description is minimal and lacks details beyond the basic purpose.

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?

Single sentence that is concise and front-loaded. No unnecessary words.

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

Completeness5/5

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

Given the simplicity (0 parameters, output schema exists), the description is complete enough. It covers the main functionality of listing characters and their expressions. Since output schema is provided, it need not describe return values.

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?

There are zero parameters, and schema description coverage is trivially 100%. Baseline for 0 parameters is 4. The description adds nothing about parameters, but none exist.

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 uses specific verb 'List available avatar characters' and includes the key detail of expressions supported. It clearly distinguishes from siblings like get_current_look (current look) and set_expression (sets expression) by focusing on listing all characters.

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

Usage Guidelines3/5

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

The description does not explicitly state when to use this tool versus alternatives. It is implied that it should be used to discover available characters and their expressions before using get_current_look or set_expression, but no explicit guidance is provided.

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

set_expressionA

Render the avatar showing the given expression, and return the image.

`emoji` must be one of the expressions the character supports (call
list_characters() to see them). `character` selects which avatar to use;
omit it to reuse the currently-selected character. The selection and
expression are remembered, so follow-up calls can just pass a new emoji.
ParametersJSON Schema
NameRequiredDescriptionDefault
emojiYes
characterNo

TDQS

A4.2/5.0
Behavior3/5

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

No annotations are provided, so the description must carry the full burden. It states that the tool returns an image and that selection/expression are remembered for follow-up calls. However, it doesn't explicitly disclose whether the tool is read-only or mutating (though rendering an image likely doesn't mutate persistent state). It also doesn't mention any side effects beyond session memory.

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 concise at three sentences, with the main action front-loaded. It packs essential details without redundancy. Every sentence adds value: purpose, parameter guidance, and statefulness reminder.

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?

Given the tool's simplicity (2 parameters, no output schema), the description covers the main points: what it does, parameter meanings, and statefulness. However, it could be more complete by specifying the return format (e.g., image URL or base64). It also doesn't explicitly warn about invalid expressions, though it points to list_characters() for discovery.

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 description adds meaning beyond the schema: it explains that emoji must be one of the character's supported expressions and that list_characters() can be used to see them. For character, it explains that omitting it reuses the current selection. This compensates for the 0% schema description coverage, though it doesn't specify the exact format or allowed values for emoji.

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 tool's purpose: to render the avatar with a given expression and return an image. It uses specific verb 'Render' and identifies the resource. It distinguishes from siblings by mentioning list_characters() for supported expressions and implies get_current_look is for viewing current 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 when-to-use context: to set an expression on an avatar. It gives guidance on the character parameter (omit to reuse current) and suggests using list_characters() to find valid expressions. It doesn't explicitly state when not to use, but the context makes it clear this is for setting expression, not for other operations like retrieving current look.

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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 3 tool updatesv0.1.0
    • First observedget_current_look
    • First observedlist_characters
    • First observedset_expression

TDQS

A4.3/5.0
Disambiguation5/5

Each tool has a distinct purpose: reporting current state, listing available options, and setting an expression. No overlap or ambiguity.

Naming Consistency5/5

All tool names use a consistent verb_noun pattern in snake_case: get_current_look, list_characters, set_expression.

Tool Count5/5

With 3 tools, the set is minimal but perfectly scoped for a character avatar service. Each tool is essential and none are missing.

Completeness5/5

The tools cover the full lifecycle: listing available characters/expressions, checking current state, and setting expression. No gaps for the intended domain.

Maintenance

ActivityStale
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

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/oldschoolsysadmin/comfyui-character-mcp'

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