mcp-toolkit-server
# mcp-toolkit-server
A production-quality [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server, written in TypeScript, that exposes a small toolkit of everyday utilities as MCP **tools**, **resources**, and **prompts**.
## What is MCP?
The **Model Context Protocol** is an open protocol from Anthropic that standardizes how applications provide context and capabilities to large language models. An MCP *server* advertises **tools** (functions the model can call), **resources** (readable data the model can pull in as context), and **prompts** (reusable prompt templates). MCP *clients* — such as Claude Desktop, IDE assistants, or custom agents — connect to servers and let the model use those capabilities.
This server speaks MCP over **stdio**: the client launches it as a subprocess and communicates via JSON-RPC on stdin/stdout. All logging is therefore sent to **stderr** so it never corrupts the protocol stream.
## What this server does
`mcp-toolkit-server` bundles a handful of self-contained utilities so you can experiment with MCP without external services or API keys. It also keeps an in-memory notes store that is exposed both as tools and as browsable resources.
## Tools
| Tool | Description | Inputs |
| --- | --- | --- |
| `calculator` | Safely evaluate a basic arithmetic expression (`+ - * / % **`, parentheses). Uses a hand-written parser — **never** `eval`. Reports divide-by-zero and invalid input as errors. | `expression: string` |
| `text_stats` | Return word, character, line, and sentence counts for a block of text. | `text: string` |
| `slugify` | Convert text into a URL-friendly slug (lowercased, diacritics stripped, hyphenated). | `text: string` |
| `base64` | Encode a UTF-8 string to base64, or decode base64 back to UTF-8. | `input: string`, `mode: "encode" \| "decode"` |
| `hash` | Compute the `md5` or `sha256` hex digest of a string. | `input: string`, `algo: "md5" \| "sha256"` |
| `uuid` | Generate one or more random v4 UUIDs. | `count?: number (1–100)` |
| `datetime` | Return the current date/time for a given IANA timezone. | `timezone: string` |
| `notes_add` | Create a new note; returns the note with its generated id. | `title: string`, `content: string` |
| `notes_list` | List all stored notes. | *(none)* |
| `notes_get` | Retrieve a single note by id. | `id: string` |
| `notes_delete` | Delete a note by id. | `id: string` |
## Resources
| Resource | URI | Description |
| --- | --- | --- |
| Server info | `server://info` | Static JSON document with server metadata (name, version, capabilities, note count). |
| Note | `note://{id}` | Dynamic resource template. Listing enumerates every stored note; reading `note://<id>` returns that note as JSON. |
## Prompts
| Prompt | Description | Arguments |
| --- | --- | --- |
| `summarize` | Ask the model to produce a concise summary of the provided text. | `text: string` |
| `code_review` | Ask the model to review a code snippet for bugs, security, and style. | `language: string`, `code: string` |
## Install & build
Requires **Node.js 18+**.
```bash
npm install
npm run build
```
This compiles `src/` to `dist/` (with type declarations).
## Run
After building, run the compiled server:
```bash
node dist/index.js
```
Or, once published/installed, via the bin entry:
```bash
npx mcp-toolkit-server
```
The server communicates over stdio, so running it directly in a terminal will simply wait for a client to connect. You should see the startup line printed to stderr:
```
mcp-toolkit-server v0.1.0 running on stdio.
```
## Add it to an MCP client (Claude Desktop)
Add an entry to your `claude_desktop_config.json` (on Windows this lives at
`%APPDATA%\Claude\claude_desktop_config.json`). Use an absolute path to the built entrypoint:
```json
{
"mcpServers": {
"toolkit": {
"command": "node",
"args": ["C:\\SourceCode\\mcp-toolkit-server\\dist\\index.js"]
}
}
}
```
Restart the client; the `toolkit` server's tools, resources, and prompts will then be available.
## Development
```bash
# Run directly from TypeScript source without building (via tsx):
npm run dev
# Type-check only:
npm run lint
# Run the test suite (Vitest):
npm test
# Watch mode:
npm run test:watch
```
The pure logic (`calculator`, `text_stats`, `slugify`, `base64`, `hash`, and the notes store) lives in dedicated modules under `src/tools/` and `src/store.ts`, and is unit-tested in `test/` without requiring a running server.
## Project structure
```
mcp-toolkit-server/
├── src/
│ ├── index.ts # Entrypoint: create server, register everything, connect via stdio
│ ├── config.ts # Server name/version constants
│ ├── store.ts # In-memory NotesStore (shared by tools and resources)
│ ├── resources.ts # server://info and note://{id} resources
│ ├── prompts.ts # summarize and code_review prompts
│ └── tools/
│ ├── index.ts # Registers all tools on the server
│ ├── calculator.ts # Safe arithmetic evaluator
│ ├── text.ts # textStats + slugify
│ ├── encoding.ts # base64 + hash
│ └── datetime.ts # timezone formatting
├── test/ # Vitest unit tests for the pure logic
├── package.json
├── tsconfig.json
├── vitest.config.ts
├── LICENSE
└── README.md
```
## License
[MIT](./LICENSE) © 2026 Nachimuthu Easwaramoorthy
TDQS
Scored across 11 tools
Each tool targets a clearly distinct purpose: math, text stats, slugification, notes CRUD, encoding, hashing, UUID generation, and datetime. Even notes_list and notes_get are clearly separated by list vs. single-item retrieval. There is no meaningful overlap or confusion between tools.
The notes tools follow a consistent notes_<verb> pattern, but the utility tools do not follow a single convention: some are nouns, some are noun_verb, and one is a bare verb. The names are individually readable and the mix is understandable, but the overall naming style is inconsistent.
Eleven tools is well within the ideal range for a general-purpose toolkit. Each utility has a standalone use case, and the notes tools form a small, focused CRUD group without unnecessary bloat.
The utility tools cover common string, text, encoding, hash, UUID, and datetime operations well. The notes store supports list, add, get, and delete, but lacks an update operation, which is a minor gap that can be worked around with delete and re-add.