mcp-hello-typescript-server
A minimal MCP demo server that provides two tools:
server_info: Returns health/status information including app name, version, uptime, supported greeting languages, default language, source, and author — useful as a sanity check that the server is running.greet: Returns a friendly greeting in one of several supported languages: English, Spanish, French, German, Italian, Portuguese, Japanese, and Hawaiian. You can specify the language by name, alternate spelling, or ISO code (e.g.,fr,Français, orfrenchall work), and optionally personalize it with a name (e.g., "Bonjour, Alice!"). Defaults to English if no language is specified. The response includes the language used, the greeting word, and the full greeting message. If an unknown language is requested, the server returns an error listing valid options.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-hello-typescript-serverGreet me in Spanish."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-hello-typescript-server
A minimal MCP server built with TypeScript
and the official
@modelcontextprotocol/sdk
— 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 repliesBonjour!.
Built with TypeScript, the TypeScript SDK,
and make. It is the TypeScript port of the sibling Python
mcp-hello-server, following the official MCP
Build a server (TypeScript)
reference. The Docker image compiles the TypeScript and runs it on a distroless
Chainguard/Wolfi Node base — no shell, no package manager, non-root.
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 Docker and an MCP client — the steps below use Claude Code and the published Docker image (nothing to build or install).
Already running one of the sibling hello servers? The Python
mcp-hello-server(aliashello), Gomcp-hello-go-server(aliashello-go), and Rustmcp-hello-rust-server(aliashello-rust) expose the sameserver_info/greettools, so it's easy to test the wrong one. Remove any you don't want registered so your client only talks tohello-ts:claude mcp list # see what's registered claude mcp remove hello # the Python server, if present claude mcp remove hello-go # the Go server, if present claude mcp remove hello-rust # the Rust server, if present
1. Add the server. Claude Code launches the container per session and talks to it over stdio:
claude mcp add hello-ts -- docker run -i --rm -e MCP_TRANSPORT=stdio ghcr.io/mitchallen/mcp-hello-typescript-server:latest2. Confirm it connected:
claude mcp list # "hello-ts" should report ✔ Connected3. 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." → (
greet→ Bonjour!)"Say hello in Japanese to Alice." → (
greet→ こんにちは (Konnichiwa), Alice!)"What languages can you greet in?" → (
server_info, readslanguages)
That round trip — the client listing tools, then calling one with arguments and getting structured JSON back — is MCP.
4. Remove it when you're done:
claude mcp remove hello-tsPrefer HTTP? Run it as a long-lived server instead:
docker run --rm -p 8000:8000 ghcr.io/mitchallen/mcp-hello-typescript-server:latest claude mcp add --transport http hello-ts http://localhost:8000/mcp
Related MCP server: HelloWorld MCP Server
Tools
Tool | Purpose |
| Health/status: app name, version, uptime, supported languages |
| Greeting in |
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, orfrall 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 a tool error listing the supported set.
Add a language
Add a row to GREETINGS in src/greetings.ts (and, optionally, an alias / ISO
code to ALIASES). server_info reports the supported set automatically.
Quick start (from source)
Requires Node.js 20+.
make install # npm ci
make build # tsc -> ./build
make test # run the test suite
make run # run the server over stdiomake help lists every target.
Running the server
stdio (default — for MCP clients that launch the server)
npm run dev # runs src/index.ts via tsx
# or, after `make build`:
node build/index.js
# or
make runStreamable HTTP (for networked clients / containers)
make run-http # PORT defaults to 8000
PORT=9000 make run-httpThe MCP endpoint is served at /mcp.
Configuration
All configuration is via environment variables:
Variable | Default | Purpose |
|
| Name reported by |
|
|
|
|
| Bind address for |
|
| Bind port for |
Using with an MCP client — local development (from source)
Point a stdio-based client (e.g. Claude Desktop, Claude Code) at the built entry point. With Claude Code, from the project directory:
make build
claude mcp add hello-ts -- node "$PWD/build/index.js"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." → (
greetwithlanguage="french"→ "Bonjour!")"Say hello in Japanese to Alice." → (
greetwithlanguage="japanese",name="Alice")"What languages can you greet in?" → (
server_info, then readlanguages)
Using a published image
The image is published to two registries:
GitHub Container Registry:
ghcr.io/mitchallen/mcp-hello-typescript-serverDocker Hub:
mitchallen/mcp-hello-typescript-server
Option A — Docker image, client launches it (stdio)
This is the simplest setup: there's nothing to build or install — just the published image. Pull it up front once so the first session doesn't block on the download (which can race an MCP client's connect/startup timeout):
docker pull ghcr.io/mitchallen/mcp-hello-typescript-server:latestThe 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:
{
"mcpServers": {
"hello-ts": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"MCP_TRANSPORT=stdio",
"ghcr.io/mitchallen/mcp-hello-typescript-server:latest",
],
},
},
}Claude Code equivalent:
claude mcp add hello-ts -- docker run -i --rm -e MCP_TRANSPORT=stdio ghcr.io/mitchallen/mcp-hello-typescript-server:latest(Pin a version like :0.1.0 in place of :latest for a reproducible setup.)
Option B — Long-running container over HTTP
The image serves HTTP by default. Start it once, then point an HTTP-capable client at it:
docker run -d --rm -p 8000:8000 --name mcp-hello-ts ghcr.io/mitchallen/mcp-hello-typescript-server:latest
claude mcp add --transport http hello-ts http://localhost:8000/mcpFor clients that only speak stdio, bridge to the HTTP endpoint with
mcp-remote:
{
"mcpServers": {
"hello-ts": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:8000/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.
The endpoint path is
/mcp.
Option C — npm package via npx (not implemented here)
Because the server already exposes a bin entry point over stdio, it could also
be distributed as an npm package and launched with npx — the usual pattern
for MCP servers in Claude Code:
claude mcp add hello-ts -- npx -y <package-name>Publishing to either the public npm registry (npmjs.com — zero-config npx)
or GitHub Packages (scoped @owner/…, but consumers need a token in
.npmrc) would enable this. It's left out of this demo on purpose to keep the
focus on the Docker distribution path.
Docker
Published multi-platform (linux/amd64, linux/arm64) images run the server
over streamable HTTP by default (MCP_TRANSPORT=http, HOST=0.0.0.0,
PORT=8000) so they're reachable on a published port.
A multi-stage build compiles the TypeScript on
cgr.dev/chainguard/node:latest-dev, prunes to production dependencies, and
copies build/ + node_modules onto a distroless
Chainguard/Wolfi node base — no shell, no
package manager, runs as the non-root node user. Unlike the Go/Rust siblings
(which ship a single static binary at ~10–17 MB), this image carries the Node
runtime and node_modules, so it's larger (~275 MB) — that's inherent to
shipping a runtime rather than a compiled binary. Every build is gated by a Trivy
scan (fails on fixable CRITICAL/HIGH); the dependency tree is separately scanned
with npm audit, and the published :latest is re-scanned daily — see
Security scanning.
Pull and run
docker pull ghcr.io/mitchallen/mcp-hello-typescript-server:latest
docker run --rm -p 8000:8000 --name mcp-hello-ts ghcr.io/mitchallen/mcp-hello-typescript-server:latestThen connect an HTTP MCP client to http://localhost:8000/mcp.
Test a published release with make
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.0 # 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 portBuild locally
make docker-build # docker build -t mcp-hello-typescript-server .
make docker-run # serves http on localhost:8000
make scan # Trivy scan of the local image (fixable CRITICAL/HIGH fail)Security scanning
Two complementary gates catch vulnerabilities, both reproducible locally:
image-scan(make scan) — Trivy scans the built container image and fails the build on fixable CRITICAL/HIGH vulnerabilities. It covers the OS layer of the runtime image and reads the JavaScript packages innode_modules.npm-audit(npm audit --omit=dev --audit-level=high) — scans the production dependency tree against the npm advisory database. Dev-only tooling advisories don't wedge the build.scan-scheduledre-scans the published:latestimage daily and uploads results to the GitHub Security tab, catching CVEs disclosed after build time.Dependabot opens weekly PRs for npm packages, the Docker base image, and GitHub Actions; low-risk updates auto-merge once CI passes.
CI / Publish
Workflows live in .github/workflows/:
ci— on every push/PR tomain: prettier format check,tsctype-check, andnode --test.npm-audit/image-scan/scan-scheduled— vulnerability scanning (see above).publish/publish-dockerhub— triggered by pushing av*tag. Build a multi-platform image, Trivy-scan it, push it to GHCR and Docker Hub, then runmake docker-testagainst the just-published image. The Docker Hub job needsDOCKERHUB_USERNAME/DOCKERHUB_TOKENrepository secrets.
To cut a release, use the release target — it bumps the version in
package.json, commits, tags, pushes, and creates the GitHub Release from the
CHANGELOG.md section, which triggers both publish workflows:
make release # patch bump (default)
make release BUMP=minor # or minor / majorThe target refuses to run unless the working tree is clean, you're on main, and
CHANGELOG.md already has a ## [X.Y.Z] section for the new version.
Docker Hub secrets (one-time setup)
Pushing to GHCR needs no setup — it uses the built-in GITHUB_TOKEN. The
publish-dockerhub job additionally needs two repository secrets and a
pre-created Docker Hub repo:
Create a Docker Hub access token (not your password) with Read & Write permissions, at hub.docker.com → Account Settings → Personal access tokens.
Create the Docker Hub repository
mitchallen/mcp-hello-typescript-server(Public).Add the two GitHub secrets —
DOCKERHUB_USERNAMEandDOCKERHUB_TOKEN:gh secret set DOCKERHUB_USERNAME --body "mitchallen" gh secret set DOCKERHUB_TOKEN # prompts for the value — paste the token
Without these, the GHCR publish job still succeeds; only publish-dockerhub
fails at the login step.
Development
Source:
src/greetings.ts— greeting data + language resolution (greet), unit-testedserver.ts—createServer()+ tools registered withserver.registerToolversion.ts— reads the version frompackage.jsonat runtimeindex.ts— the entry point; transport wiring (stdio / HTTP)
Tests:
tests/server.test.tsdrives the tools through an in-memory client (InMemoryTransport.createLinkedPair, no network/subprocess);tests/greetings.test.tsunit-tests the resolver/builder. Run everything withmake test, or the full CI gate withmake check(prettier + type-check + test).Dependencies:
package.json/package-lock.jsonare committed. Runnpm installafter changing dependencies to refresh the lockfile.
License
MIT © Mitch Allen
Available Tools
2 toolsgreetA
Return a friendly greeting in the requested language (default English). language accepts a language name, alternate spelling, or ISO code (english, spanish, french, german, italian, portuguese, japanese, hawaiian). Optional name personalizes the message. Returns {language, greeting, message}.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | Optional name to personalize the message (e.g. Bonjour, Alice!). | |
| language | No | A language name, alternate spelling, or ISO code (case-insensitive); omit to default to English. Supported: english, spanish, french, german, italian, portuguese, japanese, hawaiian. |
Output Schema
| Name | Required | Description |
|---|---|---|
| message | Yes | |
| greeting | Yes | |
| language | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It transparently describes the return format {language, greeting, message} and the flexibility of the language parameter. For a simple non-destructive tool, this is sufficient.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences, efficiently front-loading the purpose and then detailing parameters. No unnecessary words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool has an output schema (described inline) and two simple optional parameters. The description covers the return structure, language flexibility, and optional personalization, making it fully informative.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, baseline 3. The description adds value by explaining that language accepts names, alternate spellings, or ISO codes, and lists supported languages. It also clarifies the 'name' parameter personalizes the message.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool returns a friendly greeting in a requested language, defaulting to English. It distinguishes from the only sibling tool 'server_info' which is about server information.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains when to use the tool (for generating greetings) and provides details on language options. It does not explicitly state when not to use it, but with only one sibling the usage context is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
server_infoA
Health/status of the server: app name, version, uptime, and supported greeting languages.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| app | Yes | |
| author | Yes | |
| source | Yes | |
| status | Yes | |
| uptime | Yes | |
| version | Yes | |
| languages | Yes | |
| default_language | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, but description clearly lists return fields and implies read-only operation. Could be more explicit about side effects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Single sentence, 14 words, front-loaded with key information. No redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter status tool with output schema, description lists all relevant fields. Adequate for selection and invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
No parameters in schema, so description doesn't need to add parameter info. Baseline 4.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description clearly states it retrieves health/status with specific fields: app name, version, uptime, supported greeting languages. Distinct from sibling 'greet'.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Implicitly when server info is needed versus greeting with sibling. No explicit exclusions or alternatives mentioned.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
TDQS
The two tools serve entirely different purposes: one for generating greetings and one for server health. There is no overlap or ambiguity.
Both names use snake_case, but 'greet' is a verb while 'server_info' is a noun, breaking a strict verb_noun pattern. Still, the style is consistent and readable.
With only 2 tools, the server feels minimal. For a greeting-focused server, this might suffice, but it borders on too few for a typical MCP server scope.
The tools cover the core functionality (greeting and server info) with no obvious gaps. However, a dedicated tool to list supported languages could be considered a minor missing piece.
Maintenance
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
A simple Typescript MCP server built using the official MCP Typescript SDK and smithery/cli. This…
POC MCP server. Tool say_hello returns 'Welcome' (agent -> MCP -> API path).
MCP server for progressive tool usage at any scale (see https://klavis.ai)
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
Related MCP Servers
- AlicenseDqualityDmaintenanceA minimal Model Context Protocol server in TypeScript that demonstrates MCP-compliant resources and tools for LLMs, featuring simple resources and a basic tool that echoes messages or returns greetings.15Apache 2.0
- AlicenseBqualityDmaintenanceA simple TypeScript MCP server that provides two tools: one for greeting users with a customizable name and another for adding two numbers together.216MIT
- FlicenseBqualityDmaintenanceA demonstration TypeScript MCP server that showcases basic MCP concepts with simple tools (greeting, calculator), text resources, and prompt templates for learning the Model Context Protocol.2
- AlicenseNot gradedqualityDmaintenanceA simple demonstration MCP server that provides a basic 'say_hello' tool for greeting users by name, serving as a template for building MCP servers with TypeScript.131ISC
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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-typescript-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server