Skip to main content
Glama
README.md
# MCP server · TypeScript · LangChain

[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![Node.js](https://img.shields.io/badge/node-%E2%89%A520.10-brightgreen?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org/)
[![License: ISC](https://img.shields.io/badge/license-ISC-blue?style=flat-square)](LICENSE)

Minimal **[Model Context Protocol](https://modelcontextprotocol.io/)** server in **TypeScript** with a **LangChain / OpenAI** assistant behind an MCP entrypoint (`assist`), plus thin tools for deterministic JSON (`getWeather`, `addNumbers`) and a TTS helper (`textToSpeech`). Exposes MCP over **stdio** (stdin/stdout JSON-RPC)—not Express/HTTP—for hosts like Cursor, Claude Desktop, or custom clients.

<!--
[![CI](https://github.com/FedericaZuccarello/actions/workflows/ci.yml/badge.svg)](https://github.com/FedericaZuccarello/actions/workflows/ci.yml)
-->

## Why this repo

Demonstrates integrating **external LLM tooling** with a **narrow MCP surface**:

- MCP **stdio transport** + **Zod**-typed tool inputs
- **Server-side** routing: one NL entrypoint (`assist`) runs an internal agent; no extra “routing” tools on the wire
- **LangChain** `createAgent` with small domain tools (weather via Open-Meteo, math helpers)
- **TypeScript** strict mode, `tsc` build to `dist/`, demo scripts via `tsx`

## Quick start

```bash
git clone <your-fork-url>
cd mcp-server
nvm use   # optional; reads .nvmrc (Node 20)
npm install
cp .env.example .env   # set OPENAI_API_KEY for assist
npm run build
npm start              # MCP on stdio (after pre-build)
```

Try the bundled demos (they build first, then spawn `dist/mcp/index.js`):

```bash
npm run chat -- "What is the weather in London? One short sentence."
npm run mcp:demo-client -- assist '{"message":"What is the weather in London? One short sentence."}'
```

## Architecture

```mermaid
flowchart LR
  Host[MCP host] -->|stdio JSON-RPC| MCP[MCP server]
  MCP --> assist["tool: assist"]
  MCP --> getWeather["tool: getWeather"]
  MCP --> addNumbers["tool: addNumbers"]
  MCP --> textToSpeech["tool: textToSpeech (optional)"]
  assist --> Agent[LangChain agent + OpenAI]
  Agent --> W[Weather tool]
  Agent --> M[Math tools]
  getWeather --> API[Open-Meteo / geocoding]
```

### Design choices

Hosts should send **natural language through `assist`** so prompts, tool boundaries, and model config stay **on your machine**. Structured integrations can call **`getWeather`** or **`addNumbers`** directly when inputs are already known (no NL).

| MCP tool    | Purpose |
|------------|---------|
| **`assist`** | Default. `{ message }` → runs the internal LangChain assistant (tools live in-process). |
| **`getWeather`** | Raw JSON for a known city (Open-Meteo, no API key). |
| **`addNumbers`** | Raw JSON sum of two numbers. |
| **`textToSpeech`** | Text → MP3 audio (MCP audio + metadata). |

`OPENAI_API_KEY` must be visible to the **MCP server process** for **`assist`** (e.g. `.env` loaded from `dotenv`, or env vars in your host’s MCP config—**never commit secrets**).

## Layout

| Path | Role |
|------|------|
| `src/mcp/index.ts` | MCP server bootstrap, stdio transport. |
| `src/mcp/tools/assist.ts` | NL → `runAgent(...)`. |
| `src/mcp/tools/weather.ts`, `math.ts` | Thin MCP tools. |
| `src/agents/` | Registry, `run.ts`, LangChain tools, `general` agent definition. |
| `src/services/` | HTTP/domain helpers (weather, math). |
| `scripts/llm-chat.ts` | Example host (LangChain + `MultiServerMCPClient`). |
| `scripts/mcp-stdio-client-demo.ts` | Raw MCP SDK client demo. |

`npm run build` emits **`dist/`** (listed in `.gitignore`).

## Commands

| Script | What it does (demo/testing) |
|--------|--------------|
| `npm run build` | Compile with `tsc` → **`dist/`**. |
| `npm test` | Same as **`build`** (CI smoke check). |
| `npm start` | Build (prestart), then **`node dist/mcp/index.js`** (starts the MCP server). |
| `npm run dev` | **`tsx watch src/mcp/index.ts`** — no prior build. |
| `npm run chat -- "…"` | Demo host running the **whole agent system** via MCP `assist` (recommended). |
| `npm run mcp:demo-client` | Demo/testing MCP client. Call tools individually (e.g. `... -- getWeather '{"city":"London"}'`). |
| `npm run speak -- "…"` | ElevenLabs TTS smoke test (needs `ELEVENLABS_API_KEY`). Uses **`ffplay`** when on PATH; otherwise opens a temp MP3 in your default player. |

### Examples: run everything vs tools only

Run the whole agent system (natural language → `assist` → internal LangChain agent → final text):

```bash
npm run chat -- "What is the weather in London? How is 34+7?"
```

Same flow, but calling `assist` explicitly with an object payload:

```bash
npm run mcp:demo-client -- assist '{"message":"What is the weather in London? How is 34+7?"}'
```

Testing individual MCP tools (not required for the full agent):

```bash
npm run mcp:demo-client -- getWeather '{"city":"London"}'
npm run mcp:demo-client -- addNumbers '{"a":34,"b":7}'
npm run mcp:demo-client -- textToSpeech '{"text":"Hello from the server"}'
```

### Cursor & Inspector

After **`npm run build`**, point the host at **`node /absolute/path/to/dist/mcp/index.js`**.

**[MCP Inspector](https://github.com/modelcontextprotocol/inspector):** `npx -y @modelcontextprotocol/inspector node dist/mcp/index.js` from the repo root. Pass **`assist`** secrets with `-e OPENAI_API_KEY=$OPENAI_API_KEY` where needed.

## FFmpeg and ffplay (optional)

The **`npm run speak`** script can play MP3 in the terminal using **`ffplay`**, which ships with **[FFmpeg](https://ffmpeg.org/)**. If `ffplay` is not on your **PATH**, the script still works by saving a short MP3 under your temp folder and opening it with the OS default app; install FFmpeg when you want in-terminal playback without that fallback.

### Windows

1. **winget** (built into Windows 10/11):

   ```powershell
   winget install --id Gyan.FFmpeg --source winget --accept-package-agreements --accept-source-agreements
   ```

2. **Close and reopen** your terminal (or Cursor’s integrated terminal) so the updated **PATH** includes the FFmpeg `bin` directory.

3. Confirm `ffplay` is visible:

   ```powershell
   ffplay -version
   ```

If `ffplay` is still not found, add the folder that contains **`ffplay.exe`** manually: **Settings → System → About → Advanced system settings → Environment Variables → Path → Edit → New** (typical install paths look like `C:\ffmpeg\bin` or under `%LOCALAPPDATA%\Microsoft\WinGet\Packages\…`).

**Manual download:** [gyan.dev FFmpeg builds](https://www.gyan.dev/ffmpeg/builds/) (full or essentials build), unzip, then add the `bin` folder to **PATH** as above.

### macOS

```bash
brew install ffmpeg
ffplay -version
```

### Linux (Debian / Ubuntu example)

```bash
sudo apt update && sudo apt install -y ffmpeg
ffplay -version
```

Other distributions: install the **`ffmpeg`** package from your package manager; `ffplay` is included in the same package.

## Contributing

Issues and PRs welcome. Extend internal agents via `src/agents/` and **`assist.ts`** routing if you split personas.

### Checklist before you promote the repo

- Set **`author`** and (optionally) **`repository`** / **`homepage`** / **`bugs`** in [`package.json`](package.json) so npm and GitHub link correctly.
- Replace `OWNER/REPO` in the commented CI badge when the remote exists, then uncomment the line.
- Add a **GitHub profile README** or link this repo from your CV/LinkedIn—recruiters land here first from the link.
- Set the repo **About** description and topics (e.g. `mcp`, `langchain`, `typescript`, `ai-agents`).

## License

**ISC** — see [`LICENSE`](LICENSE).