Skip to main content
Glama
khughitt

Polybar Notification MCP

by khughitt

ohai

MCP for displaying Quickshell-based notifications on Linux.

Installation

npm install
npm run build

Related MCP server: ntfy-mcp-server

Tools

ohai

Display a notification popup.

Parameter

Type

Required

Description

message

string

yes

Message body

title

string

no

Popup title (defaults to message)

severity

info | warn | crit

no

Accent color (default: info)

color

string

no

Custom accent color (CSS/hex; overrides severity)

timeoutSeconds

number

no

Auto-hide delay (default: 8; 0 = persistent)

pattern

string

no

Background pattern ID or path

image

string

no

Image ID or path (default: ghost)

projectPath

string

no

Path used to resolve configured project visual defaults

workspace

string

no

Workspace to switch to on backtick

app

string

no

App/window to focus on backtick

windowId

number

no

Niri window id to focus from the notification action button

transition

glow | ghost | ripple | none

no

Entry transition effect (default: glow)

ohai_status

Check if Quickshell is running with the ohai IPC target registered.

Transition Effects

Effect

Description

glow

Soft blur glow pulse (default)

ghost

Expanding/fading copies of actual notification content

ripple

Border ripples emanating outward

none

No transition effect

Set a default transition via environment variable:

export OHAI_TRANSITION=ghost

Quickshell Setup

  1. Start the popup daemon:

qs -p /path/to/ohai-mcp/ohai/shell.qml
  1. Verify the IPC target is registered:

qs ipc -p /path/to/ohai-mcp/ohai/shell.qml --any-display show
# Should list: target ohai
  1. Test manually:

qs ipc -p /path/to/ohai-mcp/ohai/shell.qml --any-display call ohai notify "Hello" "Test message" "info" 5 "" "" "" "" "glow"
  1. Controls while popup is visible:

    • Escape - hide popup

    • Backtick (`) - switch to workspace or focus app (if configured), then hide

Testing with ohai-test

A CLI helper for testing notification appearance:

bin/ohai-test [preset]           # Run a preset
bin/ohai-test themes             # List project visual themes
bin/ohai-test theme <name>       # Preview a project visual theme
bin/ohai-test theme all          # Cycle through all project visual themes
bin/ohai-test project <path>     # Preview the project profile matched by a path
bin/ohai-test custom [options]   # Custom notification

Presets:

Preset

Description

info

Basic info notification (default)

warn

Warning notification

crit

Critical notification

long

Long text (test layout)

short

Minimal notification

claude

Claude-themed

openai

OpenAI-themed

grid

Grid pattern background

stripes

Stripes pattern background

waves

Waves pattern background

glow

Test glow transition effect

ghost

Test ghost echo transition effect

ripple

Test ripple transition effect

all

Cycle through all presets

Theme previews:

bin/ohai-test themes
bin/ohai-test theme aquatic
bin/ohai-test theme all

Project profile diagnostics:

bin/ohai-theme resolve ~/d/software/ohai
bin/ohai-test project ~/d/software/ohai

ohai-theme resolve prints JSON with the config path, normalized project path, matched project, resolved visuals, and focus target. ohai-test project sends a real notification using the same resolved profile values.

Custom options:

bin/ohai-test custom \
  -t "Title" \
  -b "Body text" \
  -s warn \
  -i claude \
  -p grid-01 \
  -d 10 \
  -c "#ff00ff" \
  -x ghost

Option

Description

-t, --title

Title text

-b, --body

Body text

-s, --severity

info | warn | crit

-i, --image

Image ID or path

-p, --pattern

Pattern ID or path

-d, --duration

Timeout in seconds

-c, --color

Custom accent color (hex)

-w, --workspace

Workspace to switch on backtick

-a, --app

App to focus on backtick

-x, --transition

glow | ghost | ripple | none

Bundled Assets

Patterns (background overlays):

  • waves-01, grid-01, stripes-01, sunset-01

Images (left panel):

  • ghost (default), claude, openai

Use an absolute path for custom assets.

Asset Trust Model

image and pattern accept bundled asset IDs or custom local asset paths. Custom paths are treated as trusted desktop input and are passed to Quickshell for rendering; ohai validates basic string shape at the MCP boundary, but it does not sandbox QML asset loading.

Prefer bundled IDs for routine notifications. Use custom absolute paths only for local files you intend Quickshell to read, and configure this MCP server only for trusted clients.

Integration

Use environment variables to configure defaults per client.

Claude Code

To add via the CLI for user scope:

claude mcp add ohai \
    --scope user \
    --env OHAI_TRANSITION=glow \
    --env OHAI_DEFAULT_IMAGE=claude \
    -- node <OHAI_PATH>/bin/ohai-mcp

~/.claude.json:

{
  "mcpServers": {
    "ohai": {
      "type": "stdio",
      "command": "node",
      "args": ["/path/to/ohai-mcp/bin/ohai-mcp"],
      "env": {
        "OHAI_DEFAULT_IMAGE": "claude",
        "OHAI_TRANSITION": "glow"
      }
    }
  }
}

Cursor

Add to MCP settings:

{
  "mcpServers": {
    "ohai": {
      "command": "node",
      "args": ["/path/to/ohai-mcp/bin/ohai-mcp"],
      "env": {
        "OHAI_DEFAULT_IMAGE": "claude",
        "OHAI_TRANSITION": "ghost"
      }
    }
  }
}

codex-cli

~/.codex/config.toml:

[mcp_servers.ohai]
command = "node"
args = ["/path/to/ohai-mcp/bin/ohai-mcp"]

[mcp_servers.ohai.env]
OHAI_DEFAULT_IMAGE = "openai"
OHAI_TRANSITION = "glow"

Auto-launch (Hyprland)

exec-once = qs -p /path/to/ohai-mcp/ohai/shell.qml

Environment Variables

Variable

Description

OHAI_DEFAULT_IMAGE

Default image when image param is not specified (e.g., claude, openai, ghost)

OHAI_TRANSITION

Default transition effect (e.g., glow, ghost, ripple, none)

OHAI_BEZEL

Bezel rendering mode. Unset (default): translucent plate suitable for compositor blur. fake: opaque plate with static noise overlay (use when compositor blur is unavailable).

OHAI_CONFIG_PATH

Override project-profile config path. Defaults to $XDG_CONFIG_HOME/ohai/config.yaml, or ~/.config/ohai/config.yaml when XDG_CONFIG_HOME is unset.

OHAI_COMPOSITOR

Override the global focus-action compositor. Supported values: hyprland, niri.

OHAI_ORIGIN_CAPTURE

Override origin-window capture. Supported values: off, focusedWindow.

NIRI_PATH

Override the niri binary used for focused-window origin capture.

Project Profiles

Use project profiles to give notifications a visual identity based on the originating project. The MCP caller passes projectPath; ohai matches it against configured project roots and applies the matched profile's theme defaults.

Config path:

  • OHAI_CONFIG_PATH when set

  • $XDG_CONFIG_HOME/ohai/config.yaml when XDG_CONFIG_HOME is set

  • ~/.config/ohai/config.yaml otherwise

Global focus actions can be made compositor-aware with compositor: hyprland or compositor: niri. OHAI_COMPOSITOR overrides the YAML value. If neither is set, workspace and app focus actions keep the existing Hyprland command shape. Origin capture is opt-in with originCapture: focusedWindow or OHAI_ORIGIN_CAPTURE=focusedWindow.

Example:

compositor: niri
originCapture: focusedWindow

projects:
  - id: ohai
    name: ohai
    roots:
      - ~/d/software/ohai
    theme: aquatic
    workspace: dev

  - id: infra
    name: Infrastructure
    roots:
      - ~/d/infra
    theme: terminal
    transition: ripple

Built-in themes:

Theme

Image

Color

Pattern

Transition

ghost

ghost

#7ad7ff

none

ghost

aquatic

openai

#7ad7ff

waves-01

glow

frog

ghost

#c3e88d

grid-01

ripple

ember

claude

#ff9e64

stripes-01

glow

violet

openai

#bb9af7

grid-01

ghost

terminal

ghost

#9ece6a

stripes-01

none

sunset

claude

#f7768e

sunset-01

ripple

matrix

openai

#73daca

grid-01

glow

Profile matching uses normalized roots and chooses the longest matching prefix. Visual precedence is: explicit ohai tool input, then project overrides, then theme defaults, then environment defaults and built-in defaults. Unknown themes make config loading fail early.

Profiles may also define one focus target for notification actions:

compositor: niri
originCapture: focusedWindow

projects:
  - id: ohai
    roots:
      - ~/d/software/ohai
    theme: aquatic
    workspace: dev

  - id: terminal-tools
    roots:
      - ~/d/tools
    theme: ghost
    app: kitty

Supported profile focus fields are workspace, app, and windowId. Specify only one per profile. Explicit workspace, app, or windowId values passed to the ohai tool override the matched project profile focus target.

With compositor: niri, workspace uses niri msg action focus-workspace <workspace> and windowId uses niri msg action focus-window --id <id>. app focus is Hyprland-only because niri does not expose a direct app-title focus action; for niri use workspace or windowId.

With originCapture: focusedWindow, ohai runs niri msg --json focused-window when sending a notification without an explicit or project-profile focus target. If niri returns a focused window id, ohai uses that id as the notification action target. Focus precedence is: explicit MCP target, then project profile target, then captured focused window, then no focus target.

Use the CLI helpers to inspect profile matching before wiring an MCP client:

bin/ohai-theme resolve ~/d/software/ohai

Example shape, with paths abbreviated for readability:

{
  "configPath": "~/.config/ohai/config.yaml",
  "projectPath": "~/d/software/ohai",
  "matched": true,
  "project": {
    "id": "ohai",
    "name": "ohai",
    "theme": "aquatic",
    "matchedRoot": "~/d/software/ohai"
  },
  "compositor": "niri",
  "originCapture": "focusedWindow",
  "visuals": {
    "image": "openai",
    "color": "#7ad7ff",
    "pattern": "waves-01",
    "transition": "glow"
  },
  "focus": {
    "type": "workspace",
    "value": "dev"
  }
}

When no profile matches, matched is false and project is null.

For the frosted-glass bezel effect, add the following to your hyprland.conf:

layerrule = blur, ^ohai$
layerrule = ignorezero, ^ohai$

The anchored regex (^ohai$) ensures the rule applies only to the ohai notification surface. ignorezero tells Hyprland to skip blur on transparent regions of the surface — important because the popup window includes transparent padding around the visible bezel for transition-effect overflow; without this rule, the entire rectangular surface gets blurred.

Verify with hyprctl layers after reloading Hyprland — you should see ohai listed with the rules applied.

If you don't add these rules, the bezel will render as a translucent dark plate without compositor blur. This is still acceptable, just less visually distinct. Alternatively, set OHAI_BEZEL=fake for a more opaque bezel with a static noise texture.

Architecture

┌─────────────┐     qs ipc call      ┌─────────────────┐
│  MCP Client │ ──────────────────▶  │   Quickshell    │
│  (Claude)   │                      │   (shell.qml)   │
└─────────────┘                      └─────────────────┘
       │                                     │
       │                                     │
       ▼                                     ▼
┌─────────────┐                      ┌─────────────────┐
│  ohai-mcp   │                      │  IpcHandler     │
│  (Node.js)  │                      │  target: "ohai" │
└─────────────┘                      └─────────────────┘

The MCP server communicates with Quickshell via its built-in IPC mechanism (qs ipc call), which uses a Unix socket for instant, reliable delivery.

The internal notify IPC call takes 9 ordered arguments: title, body, severity, timeoutSeconds, pattern, image, actionTarget, color, transition. actionTarget is empty, an app/window title, workspace:<id>, window:<id>, or a compositor-prefixed target such as niri:workspace:<name> or hyprland:app:<title>.

Development

npm run dev      # Watch mode
npm run build    # Build
npm run lint     # Lint
npm run format   # Format

Requirements

  • Linux with Quickshell

  • Node.js 18+

Credit

Original implementation adapted from neotanx/neomcps - sound notification mcp.

Available Tools

2 tools
display_polybar_messageA

Display a message in polybar status bar. Useful for notifying the user when an operation is complete or when waiting for user input.

ParametersJSON Schema
NameRequiredDescriptionDefault
backgroundNoBackground color for the message (default: #333333)
colorNoText color for the message (default: #ffffff)
durationNoDuration in seconds to display the message (default: 5)
messageYesThe message to display in polybar

TDQS

A3.7/5.0
Behavior3/5

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

With no annotations provided, the description carries the full burden. It describes the tool's purpose and use cases but lacks details on behavioral traits such as permissions needed, error handling, or whether it's read-only or destructive. The description doesn't contradict annotations (none exist), but it's minimal on behavioral context.

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 appropriately sized with two concise sentences that directly state the tool's function and its usefulness. It's front-loaded with the core purpose and avoids unnecessary details, making it efficient and easy to understand.

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?

Given the tool's moderate complexity (4 parameters, no output schema, no annotations), the description is adequate but incomplete. It covers purpose and usage but lacks details on behavioral aspects like return values or error conditions. With no output schema, it should ideally mention what happens after display, but it's minimally viable.

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?

Schema description coverage is 100%, so the schema already documents all parameters thoroughly. The description adds no additional parameter semantics beyond what's in the schema, such as explaining parameter interactions or edge cases. This meets the baseline of 3 when schema coverage is high.

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 ('Display a message') and target ('in polybar status bar'), providing a specific verb+resource combination. However, it doesn't explicitly differentiate from the sibling tool 'show_popup_notification' beyond mentioning polybar specifically, which is good but not fully comparative.

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 for when to use this tool ('Useful for notifying the user when an operation is complete or when waiting for user input'), giving practical scenarios. It doesn't explicitly state when not to use it or name alternatives like the sibling tool, but the context is sufficient for informed usage.

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

show_popup_notificationB

Show a popup notification using notify-send/dunst. Useful for notifying the user when an operation is complete or when waiting for user input.

ParametersJSON Schema
NameRequiredDescriptionDefault
iconNoIcon name or path for the notification
messageYesThe notification message
timeoutNoNotification timeout in milliseconds (default: 5000)
titleYesThe notification title
urgencyNoNotification urgency level (default: normal)

TDQS

B3.3/5.0
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions the tool uses 'notify-send/dunst' which implies system-level notification behavior, but it doesn't disclose critical traits like whether it requires specific permissions, if it's synchronous/asynchronous, error handling, or platform dependencies. For a tool with no annotation coverage, this leaves significant gaps in understanding its operational behavior.

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 extremely concise and well-structured: two sentences that directly state the tool's purpose and usage context. Every word earns its place with no redundancy or fluff. It's front-loaded with the core functionality and follows with practical guidance.

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?

Given the tool's moderate complexity (5 parameters, no output schema, no annotations), the description provides basic completeness but has gaps. It covers what the tool does and when to use it at a high level, but lacks details on behavioral traits, error conditions, or integration with the sibling tool. Without annotations or output schema, more context would be helpful for full understanding.

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?

Schema description coverage is 100%, with all 5 parameters well-documented in the schema (e.g., 'message' as notification content, 'urgency' with enum values). The description adds no parameter-specific information beyond what's in the schema. According to the rules, with high schema coverage (>80%), the baseline is 3 even with no param info in the description.

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 tool's purpose: 'Show a popup notification using notify-send/dunst.' It specifies the verb ('show'), resource ('popup notification'), and implementation method. However, it doesn't explicitly differentiate from its sibling tool 'display_polybar_message' beyond mentioning 'popup' vs. 'polybar' in names, leaving some ambiguity about when to choose one over the other.

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 provides implied usage guidance: 'Useful for notifying the user when an operation is complete or when waiting for user input.' This gives context on when to use it, but it doesn't explicitly state when NOT to use it or mention the sibling tool as an alternative. The guidance is helpful but lacks explicit exclusions or comparisons.

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.

  1. 2 tool updatesv1.0.0
    • First observeddisplay_polybar_message
    • First observedshow_popup_notification

TDQS

B3.2/5.0

Scored across 2 tools

Disambiguation2/5

The two tools have overlapping purposes—both are described as 'useful for notifying the user when an operation is complete or when waiting for user input.' This creates ambiguity, as an agent might struggle to choose between displaying a message in the polybar status bar versus showing a popup notification, since their use cases are not clearly differentiated. The descriptions do not specify distinct scenarios or advantages for each tool, leading to potential misselection.

Naming Consistency5/5

The tool names follow a consistent verb_noun pattern with clear, descriptive terms: 'display_polybar_message' and 'show_popup_notification.' Both use snake_case and start with action verbs ('display' and 'show'), making them predictable and easy to understand. There are no deviations in naming conventions, ensuring readability and coherence.

Tool Count2/5

With only 2 tools, the server feels thin for a notification domain that could benefit from more granular operations, such as different notification types, priorities, or durations. While the count is minimal, it may be too few to cover common notification workflows effectively, limiting the server's utility and causing agents to work around gaps in functionality.

Completeness2/5

Inferring the domain as user notifications, the tool set is severely incomplete. It lacks operations for managing notifications (e.g., dismissing, updating, or clearing notifications), handling different notification styles or priorities, or integrating with other notification systems. This creates significant gaps that could lead to agent failures when trying to perform common notification-related tasks beyond basic display.

Maintenance

ActivityInactive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers