Skip to main content
Glama
mitchallen

mcp-hello-server

by mitchallen

mcp-hello-server

GitHub tag PyPI Python versions PyPI downloads Docker Hub Docker image size Docker pulls test bdd image-scan

Note: the version and download badges above are cached images (shields.io, and PyPI's own image proxy), so they can lag reality by a while — a version badge showing an older number doesn't mean an old release. For the authoritative current version, check the latest release / tag, the version heading on the PyPI project page itself, or run the server's server_info tool. The downloads badge is PyPI's monthly count via pypistats.org (derived from the public BigQuery dataset, not a live PyPI counter), so a brand-new release can read 0/no-data for a day or two until the batch catches up.

A minimal MCP server built with Python and FastMCP — a good starting point for a new server or a demo. It exposes just two tools:

  • server_info — a health/status check.

  • greet — a friendly greeting in one of a handful of languages, defaulting to English. Ask it to "greet in French" and it replies Bonjour!.

Built with Python, uv, FastMCP, and make, and distributed two ways so you can run it however you like:

It was scaffolded from the sibling random-mcp-server by stripping it down to server_info and adding the greet demo tool.


Quick start — demo an MCP server in 2 minutes

New to MCP? This is a tiny, safe server for seeing how an MCP client discovers and calls tools. Every tool is a harmless in-memory lookup, so it's a good sandbox. All you need is an MCP client and either uv (which provides uvx) or Docker — the steps below use Claude Code (nothing to build or clone), with the equivalent Hermes Agent commands at the end.

1. Add the server. Pick whichever runtime you have — Claude Code launches it per session and talks to it over stdio:

# Python (no Docker) — runs the PyPI package via uvx (needs uv installed),
# downloading it on first use:
claude mcp add hello -- uvx mcp-hello-server

# …or Docker — runs the published image:
claude mcp add hello -- docker run -i --rm -e MCP_TRANSPORT=stdio mitchallen/mcp-hello-server:latest

2. Confirm it connected:

claude mcp list        # "hello" should report ✔ Connected

3. Ask in plain language — Claude discovers the tools and picks one (the tool it calls is in parentheses):

  • "Is the hello server up? What version is it?" → (server_info)

  • "Greet me in French." → (greetBonjour!)

  • "Say hello in Japanese to Alice." → (greetこんにちは (Konnichiwa), Alice!)

  • "What languages can you greet in?" → (server_info, reads languages)

That round trip — the client listing tools, then calling one with arguments and getting structured JSON back — is MCP. Peek at the tool schemas the client sees with make dev (the FastMCP Inspector), or read Tools below.

4. Remove it when you're done:

claude mcp remove hello

Prefer HTTP? Run it as a long-lived server instead — with Python or Docker:

MCP_TRANSPORT=http uvx mcp-hello-server              # Python, serves on :8000
# …or: docker run --rm -p 8000:8000 mitchallen/mcp-hello-server:latest
claude mcp add --transport http hello http://localhost:8000/mcp

See Using a published image or a remote server for other clients and the mcp-remote bridge.

Want it installed, not ephemeral? uvx fetches and runs the package without installing it. To keep it on your PATH, install the PyPI package instead:

pipx install mcp-hello-server        # or: pip install mcp-hello-server
mcp-hello-server                     # the console script runs the same server

Hermes Agent

Hermes Agent takes the command and its arguments as separate flags rather than after a -- separator. Register the server, then check the connection:

hermes mcp add hello --command uvx --args mcp-hello-server
hermes mcp test hello

As with the Claude Code route above, uvx needs uv installed and downloads the PyPI package on first use — nothing to clone or build. Once it connects, the same plain-language prompts in step 3 apply; the server is identical, only the client differs.

Hermes also reads MCP servers from its config.yaml (command:/args: for stdio, url: for an HTTP endpoint like the one in the note above), which is the better route for anything beyond a one-liner; run /reload-mcp in a session to pick up edits. See the Hermes MCP guide for the full configuration reference.


Related MCP server: mcpscope

Tools

Tool

Title

Purpose

server_info()

Server Info

Health/status: app name, version, uptime, supported languages

greet(language?, name?)

Greet

Greeting in language (default English); optional name

greet

greet takes two optional arguments:

  • language — a language name, an alternate spelling, or an ISO code (case-insensitive). Omit it to default to English. Supported: english, spanish, french, german, italian, portuguese, japanese, hawaiian (e.g. french, Français, or fr all work).

  • name — optional; personalizes the message (Bonjour, Alice!).

It returns { language, greeting, message }:

// greet(language="french")
{ "language": "french", "greeting": "Bonjour", "message": "Bonjour!" }

// greet(language="spanish", name="Alice")
{ "language": "spanish", "greeting": "Hola", "message": "Hola, Alice!" }

// greet()  -> { "language": "english", "greeting": "Hello", "message": "Hello!" }

An unknown language returns an error listing the supported set.

Annotations

Both tools carry MCP tool annotations — hints a client can use to decide how to present a tool and whether it needs a confirmation prompt:

Annotation

Value

Meaning

readOnlyHint

true

The tool does not modify its environment

openWorldHint

false

It touches no external entities — no network, no disk

Together these say both tools are safe reads against in-process data, so a client can list or auto-approve them without prompting. Each also has a human-readable title (see the table above) for display in place of the raw function name.

destructiveHint and idempotentHint are deliberately not set: the MCP spec defines them as meaningful only when readOnlyHint is false, so setting them on a read-only tool would imply significance they don't carry. A new tool that writes state or reaches the network should flip readOnlyHint/openWorldHint and then set them.

Annotations are hints, not a security boundary — a client is free to ignore them, and they describe the server's own claims about its tools.

Add a language

Add a row to GREETINGS in src/mcp_hello_server/greetings.py (and, optionally, an alias / ISO code in _ALIASES). server_info reports the supported set automatically.


Quick start

Requires uv.

make install     # create .venv and sync deps
make test        # run the test suite
make run         # run the server over stdio

make help lists every target.


Running the server

stdio (default — for MCP clients that launch the server)

uv run mcp-hello-server
# or
make run

Streamable HTTP (for networked clients / containers)

make run-http            # PORT defaults to 8000
PORT=9000 make run-http

Inspect the server

make inspect             # print a summary: name, version, tool count
make dev                 # launch the interactive FastMCP Inspector (web UI)

Configuration

All configuration is via environment variables:

Variable

Default

Purpose

APP_NAME

mcp-hello-server

Name reported by server_info

MCP_TRANSPORT

stdio

stdio, http, or sse

HOST

127.0.0.1

Bind address for http/sse

PORT

8000

Bind port for http/sse


Using with an MCP client — local development (from source)

Point a stdio-based client (e.g. Claude Desktop, Claude Code) at the console script. Example claude_desktop_config.json entry using uv:

{
  "mcpServers": {
    "hello": {
      "command": "uv",
      "args": ["run", "--directory", "/absolute/path/to/mcp-hello-server", "mcp-hello-server"]
    }
  }
}

With Claude Code:

claude mcp add hello -- uv run --directory "$PWD" mcp-hello-server

Confirm it's connected with claude mcp list (or /mcp inside a session).

Example prompts (Claude Code)

Once the server is added, just ask in plain language — Claude picks the right tool. The tool it invokes is shown in parentheses.

  • "Is the hello server up? What version is it?" → (server_info)

  • "Greet me." → (greet, defaults to English → "Hello!")

  • "Greet in French." → (greet with language="french" → "Bonjour!")

  • "Say hello in Japanese to Alice." → (greet with language="japanese", name="Alice")

  • "What languages can you greet in?" → (server_info, then read languages)


Using a published image or a remote server

This section is for consumers who are not building from source — you have the published Docker image, or someone has deployed the server for you.

Option A — Docker image, client launches it (stdio)

The client starts a fresh container per session and talks to it over stdio. Use -i (keep stdin open) and force the stdio transport, since the image defaults to HTTP. The image is published to two registries, so pick one:

// GitHub Container Registry (GHCR)
{
  "mcpServers": {
    "hello": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT=stdio",
               "ghcr.io/mitchallen/mcp-hello-server:latest"]
    }
  }
}
// Docker Hub
{
  "mcpServers": {
    "hello": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT=stdio",
               "mitchallen/mcp-hello-server:latest"]
    }
  }
}

Claude Code equivalent — again, pick a registry:

# GitHub Container Registry (GHCR)
claude mcp add hello -- docker run -i --rm -e MCP_TRANSPORT=stdio ghcr.io/mitchallen/mcp-hello-server:latest

# Docker Hub
claude mcp add hello -- docker run -i --rm -e MCP_TRANSPORT=stdio mitchallen/mcp-hello-server:latest

(Pin a version like :0.1.2 in place of :latest for a reproducible setup. Add --scope user to register the server for every project on your machine.)

Option B — Long-running container over HTTP (local)

Start the container once (it serves HTTP by default) from either registry, then point an HTTP-capable client at it:

# GitHub Container Registry (GHCR)
docker run -d --rm -p 8000:8000 --name mcp-hello ghcr.io/mitchallen/mcp-hello-server:latest

# Docker Hub
docker run -d --rm -p 8000:8000 --name mcp-hello mitchallen/mcp-hello-server:latest

Claude Code (native HTTP transport):

claude mcp add --transport http hello http://localhost:8000/mcp

For clients that only speak stdio, bridge to the HTTP endpoint with mcp-remote:

{
  "mcpServers": {
    "hello": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://localhost:8000/mcp"]
    }
  }
}

Option C — Remote deployment (HTTP)

If the server is hosted elsewhere, use its public URL — everything else matches Option B:

claude mcp add --transport http hello https://mcp-hello.example.com/mcp

Notes for remote use:

  • Prefer HTTPS so traffic is encrypted in transit.

  • This server ships no authentication. If you expose it beyond localhost, put it behind a reverse proxy, gateway, or network policy — or add FastMCP auth.

  • The endpoint path is /mcp (no trailing slash). Requesting /mcp/ works too but returns a 307 redirect to /mcp.


Docker

Published multi-platform (linux/amd64, linux/arm64) images are available from two registries:

  • GitHub Container Registry: ghcr.io/mitchallen/mcp-hello-server

  • Docker Hub: mitchallen/mcp-hello-server

The image runs the server over streamable HTTP by default (MCP_TRANSPORT=http, HOST=0.0.0.0, PORT=8000) so it's reachable on a published port.

It's built on a distroless Chainguard/Wolfi Python base — no shell or package manager, runs as a non-root user, and scans 0 known vulnerabilities. Every build is gated by a Trivy scan (fails on fixable CRITICAL/HIGH) and the published :latest is re-scanned daily; see CI / Publish.

Pull and run

# GitHub Container Registry (GHCR)
docker pull ghcr.io/mitchallen/mcp-hello-server:latest
docker run --rm -p 8000:8000 --name mcp-hello ghcr.io/mitchallen/mcp-hello-server:latest

# Docker Hub
docker pull mitchallen/mcp-hello-server:latest
docker run --rm -p 8000:8000 --name mcp-hello mitchallen/mcp-hello-server:latest

Pin a specific release instead of :latest for a reproducible setup, e.g. ghcr.io/mitchallen/mcp-hello-server:0.1.2. Then connect an HTTP MCP client to http://localhost:8000/mcp.

Test a published release with make

Convenience targets pull and run the published image in your local Docker environment — handy for smoke-testing a release without a local build:

make docker-test               # up + smoke + down in one shot (exits non-zero on failure)

make docker-up                 # pull + run ghcr.io/mitchallen latest, detached
make docker-smoke              # MCP `initialize` handshake — passes if the server responds
make docker-down               # stop it

make docker-up TAG=0.1.2                         # pin a version
make docker-up REGISTRY=docker.io/mitchallen     # pull from Docker Hub instead
make docker-up HTTP_PORT=9000                    # publish on a different host port

Build locally

make docker-build        # docker build -t mcp-hello-server .
make docker-run          # serves http on localhost:8000

CI / Publish

Three kinds of GitHub Actions workflows live in .github/workflows/:

  • test — runs on every push/PR to main: the unit suite (pytest --ignore=tests/test_bdd.py).

  • bdd — runs the pytest-bdd scenarios (pytest tests/test_bdd.py) in its own workflow so it passes/badges independently of the unit suite.

  • publish / publish-dockerhub — triggered by pushing a v* tag. Build a multi-platform image and push it to GHCR and Docker Hub, then run make docker-test against the just-published image as a post-publish smoke check. The Docker Hub job needs DOCKERHUB_USERNAME / DOCKERHUB_TOKEN repository secrets and a pre-created mitchallen/mcp-hello-server repo.

  • publish-pypi — also triggered by the v* tag (and can be run manually via workflow_dispatch). Runs the suite on Python 3.11–3.13, then builds and uploads the sdist + wheel to PyPI using trusted publishing (OIDC — no stored token). It needs a matching PyPI publisher configured for this repo, workflow publish-pypi.yml, and a pypi GitHub environment.

To cut a release, use the release target — it bumps version in pyproject.toml (and uv.lock), commits, tags, and pushes, which triggers all three publish workflows (GHCR, Docker Hub, PyPI):

make release              # patch bump (default)
make release BUMP=minor   # or minor / major

The target refuses to run unless the working tree is clean and you're on main.


Development

  • Source: src/mcp_hello_server/

    • greetings.py — greeting data + language resolution (greet)

    • server.py — FastMCP tools + entry point (main)

  • Tests: tests/, run with make test (uv run pytest), driven through an in-memory FastMCP client. Layers:

    • test_greetings.py — plain pytest unit tests for the resolver/builder.

    • test_server.py — the tools through the in-memory client.

    • test_bdd.py + tests/features/*.feature — a pytest-bdd layer.

  • make build produces a wheel/sdist via uv build.

  • Dependencies: uv.lock is committed and the Docker build installs from it with --frozen. Whenever you change dependencies in pyproject.toml, run make lock (or uv lock) to refresh the lockfile and commit it.


License

MIT © Mitch Allen

Available Tools

2 tools
greetGreetA
Read-only

Return a friendly greeting in the requested language (default English).

language accepts a language name, an alternate spelling, or an ISO code (case-insensitive) — e.g. "french", "Français", or "fr". Supported languages: english, spanish, french, german, italian, portuguese, japanese, hawaiian. Pass an optional name to personalize the message (e.g. "Bonjour, Alice!"). Returns {language, greeting, message}.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNo
languageNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior4/5

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

The readOnlyHint annotation already signals no side effects, but the description adds specific behavior: accepted language formats, supported language list, default-to-English when language is unset, and the exact return shape {language, greeting, message}. This goes beyond the annotations without contradicting them.

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 compact, front-loaded with the core purpose, and every sentence adds value: language flexibility, supported languages, name personalization, and return shape. There is no jargon or filler.

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?

For a simple two-parameter, zero-required tool with an output schema and read-only annotation, the description fully covers what an agent must know to invoke it correctly. It explains input formats, defaults, personalization, and expected output, leaving no significant gap.

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

Parameters5/5

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

Schema description coverage is 0%, so the description carries the full burden for the two parameters. It thoroughly explains 'language' as accepting names, alternate spellings, or ISO codes case-insensitively, enumerates allowed values, and explains the optional 'name' for personalization. This fully compensates for the empty schema descriptions.

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 opens with a specific action and resource: 'Return a friendly greeting in the requested language.' It is immediately clear what the tool does and how it differs from the sibling server_info, which provides server metadata.

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 implies when to use it by defining its purpose and parameters, but it does not explicitly say 'use this when the user wants a greeting' or contrast it with sibling server_info. Usage context is clear enough for an agent, but there is no explicit guidance about when not to use it or what alternatives exist.

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

server_infoServer InfoB
Read-only

Health/status of the server.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.4/5.0
Behavior2/5

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

Annotations already declare readOnlyHint=true, and the description adds no extra behavioral context beyond restating the high-level purpose. No mention of potential latency, authentication, errors, or what specific statuses are returned; this is not a meaningful behavior disclosure beyond the annotation.

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

Conciseness4/5

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

The short fragment 'Health/status of the server.' is direct and front-loaded with the key purpose, with no filler. It is a bit terse but appropriate for a zero-parameter status tool.

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 that there are no parameters, an explicit output schema exists, and annotations signal non-mutating behavior, the description suffices for basic selection and invocation. It only lacks a little more explicit detail about what 'health/status' includes, but the schema and purpose cover the core context.

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 no parameters, so the baseline for parameter semantics is 4. The description correctly implies a parameterless lookup and does not need to compensate for any schema deficiency.

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 identifies the resource (server) and the kind of output (health/status), distinguishing this tool from a conversational sibling like greet. It lacks an explicit verb, but the intent is unmistakable for an info-gathering tool.

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 need for server health/status is clear and implicitly separates it from greet, but no explicit when-to-use guidance or exclusions are provided. The description leaves the agent to infer that this tool is the correct choice only when server status is needed.

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. 2 tool updatesv0.1.0
    • First observedgreet
    • First observedserver_info

TDQS

A3.9/5.0
Disambiguation5/5

server_info and greet have clearly distinct purposes: one reports server status, the other returns personalized greetings. There is no overlap or ambiguity between them.

Naming Consistency4/5

The two tool names are clear and readable, though one uses a noun-based name (server_info) and the other uses a verb-based name (greet). The mild inconsistency is not confusing at this scale.

Tool Count4/5

Two tools is minimal but well-suited to a simple hello server. While it borders on thin, each tool serves an obvious purpose and the scope is intentionally narrow.

Completeness5/5

The tool set fully covers the apparent domain of a hello server: checking server health and returning greetings. It supports language selection, personalization, and multiple languages, leaving no obvious dead ends.

Maintenance

ActivityActive
ResponsivenessNo issues

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

  • -
    license
    C
    quality
    Not graded
    maintenance
    A simple boilerplate MCP server that provides a basic greeting tool for demonstration purposes. Serves as a starting template for developers to quickly create and deploy custom MCP servers.
    1
    16
    -
  • F
    license
    A
    quality
    D
    maintenance
    A simple local MCP server that provides greeting and integer addition tools.
    2
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    A minimal MCP server offering a hello_world tool that returns a greeting and current UTC time via streamable HTTP.
    -

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/mitchallen/mcp-hello-server'

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