Skip to main content
Glama
webski101
by webski101
README.md
<div align="center">

# minimax-llm-mcp

**An [MCP](https://modelcontextprotocol.org) server that exposes the MiniMax M3 LLM API to MCP-compatible clients.**

[![npm version](https://img.shields.io/npm/v/minimax-llm-mcp.svg)](https://www.npmjs.com/package/minimax-llm-mcp)
[![License: MIT](https://img.shields.io/npm/l/minimax-llm-mcp.svg)](./LICENSE)
[![Node engine](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)

MCP server that exposes the MiniMax M3 LLM API to MCP-compatible clients over stdio and SSE.

</div>

---

## Overview

`minimax-llm-mcp` is a [Model Context Protocol](https://modelcontextprotocol.org) (MCP) server that exposes the [MiniMax M3 LLM API](https://api.minimax.io) to any MCP-compatible client — Claude Desktop, Cursor, CyOps, Windsurf, and others. The server speaks JSON-RPC over **stdio** (the default, suitable for child-process clients) and **HTTP + Server-Sent Events** (SSE, for browser- and network-based clients), and registers four tools that map cleanly onto the upstream's chat-completions and tool-use surface:

| Tool | Purpose |
| --- | --- |
| `minimax_chat` | Non-streaming chat completion. |
| `minimax_complete` | Single-turn text completion (prompt + optional system). |
| `minimax_tool_call` | M3-native tool-use passthrough. Forwards `tools` and `tool_choice` verbatim. |
| `minimax_count_tokens` | Local token count using `cl100k_base` (no upstream call). |

The MiniMax M3 endpoint is OpenAI-compatible; the server wraps a small, well-tested HTTP client that handles auth, timeouts, retry-on-429, error mapping, and request-secret redaction.

> **Status:** `0.1.0` — the binary, the four tools, and the stdio + SSE transports are wired up. The SSE transport is feature-complete but not exercised by the demo at this time.

---

## Features

- **MCP-native** — registers four tools with Zod-validated input schemas, conforming to the [MCP spec](https://modelcontextprotocol.org/specification).
- **Two transports** — stdio (default) for child-process clients, and HTTP+SSE for network clients.
- **OpenAI-compatible** — non-streaming and streaming chat completions, plus native tool-use passthrough.
- **Local token counting** — `minimax_count_tokens` runs entirely client-side via `gpt-tokenizer`'s `cl100k_base` encoding; no upstream call, deterministic, fast.
- **Production-grade HTTP** — `Authorization: Bearer …` on every request, configurable per-request timeout, one-shot retry on `429` with exponential backoff, and full HTTP-status → `McpError` mapping (401/403 → `AuthenticationRequired`, 429 → `RateLimited`, 5xx → `UpstreamError`, other 4xx → `InvalidRequest`).
- **Secret redaction** — error messages are scrubbed of `sk-…` API-key shapes before they leave the server.
- **TypeScript-native** — strict ES2022 / NodeNext / `tsup`-bundled CJS with declarations on the wire.
- **Tested** — `vitest` with `v8` coverage; 80%+ line coverage on the runtime modules.

---

## Installation

### From npm (recommended)

```bash
npm install -g minimax-llm-mcp
```

This installs the `minimax-llm-mcp` binary on your `PATH`, ready for any MCP client to spawn.

### From a local checkout

```bash
git clone https://github.com/your-org/minimax-llm-mcp.git
cd minimax-llm-mcp
npm install
npm run build
```

The compiled binary is then at `./dist/index.js`. Point your MCP client at it directly (see [Usage](#usage) below).

### Prerequisites

- **Node.js ≥ 18** (the `engines` field enforces this).
- A **MiniMax API key**. Sign up at the [MiniMax developer portal](https://api.minimax.io) and copy the bearer token from your dashboard.

---

## Configuration

The server reads its configuration from environment variables at startup. The schema is validated by Zod; missing or invalid values produce a `ConfigError` and exit 1 on stdio, or `500` on SSE.

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `MINIMAX_API_KEY` | **yes** | _(none)_ | Bearer token for the MiniMax M3 LLM API. |
| `TRANSPORT` | no | `stdio` | Transport the server listens on. One of `stdio` or `sse`. |
| `REQUEST_TIMEOUT_MS` | no | `300000` | Per-request timeout when calling the upstream API (in milliseconds). |
| `RETRY_ON_429` | no | `true` | Whether to retry once on `429 Too Many Requests` with a short back-off. |
| `MINIMAX_EMBEDDING_ENABLED` | no | `false` | Reserved for a future `minimax_embed` tool (out of scope in `0.1.0`). |

The full set is also documented in [`.env.example`](./.env.example) — copy that file to `.env` and uncomment the lines you want to override:

```bash
cp .env.example .env
$EDITOR .env
```

---

## Usage

The server is consumed by an MCP client. Below are copy-pasteable configuration snippets for the four most common clients. Replace `<your-minimax-api-key>` with a real bearer token, or set `MINIMAX_API_KEY` in the client's environment.

### Claude Desktop

Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```json
{
  "mcpServers": {
    "minimax-llm-mcp": {
      "command": "npx",
      "args": ["-y", "minimax-llm-mcp"],
      "env": {
        "MINIMAX_API_KEY": "<your-minimax-api-key>"
      }
    }
  }
}
```

Or, if you have a local build:

```json
{
  "mcpServers": {
    "minimax-llm-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/minimax-llm-mcp/dist/index.js"],
      "env": {
        "MINIMAX_API_KEY": "<your-minimax-api-key>"
      }
    }
  }
}
```

### Cursor

Edit `~/.cursor/mcp.json` (or use Settings → MCP → Add new global MCP server):

```json
{
  "mcpServers": {
    "minimax-llm-mcp": {
      "command": "npx",
      "args": ["-y", "minimax-llm-mcp"],
      "env": {
        "MINIMAX_API_KEY": "<your-minimax-api-key>"
      }
    }
  }
}
```

### CyOps

CyOps reads MCP servers from its global config (`~/.cyops/mcp.json` or the in-app Settings → MCP panel):

```json
{
  "mcpServers": {
    "minimax-llm-mcp": {
      "command": "npx",
      "args": ["-y", "minimax-llm-mcp"],
      "env": {
        "MINIMAX_API_KEY": "<your-minimax-api-key>"
      }
    }
  }
}
```

### Windsurf

Edit `~/.codeium/windsurf/mcp_config.json` (or the in-app Settings → Cascade → MCP Servers → Add server form):

```json
{
  "mcpServers": {
    "minimax-llm-mcp": {
      "command": "npx",
      "args": ["-y", "minimax-llm-mcp"],
      "env": {
        "MINIMAX_API_KEY": "<your-minimax-api-key>"
      }
    }
  }
}
```

### Trying it without an MCP client

For a quick smoke test (no real API call required):

```bash
# In one terminal, run the server in stdio mode and pipe a JSON-RPC
# `tools/list` request through it:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
  | MINIMAX_API_KEY=demo-key npx minimax-llm-mcp
```

The server reads the request from stdin, dispatches it, and writes the JSON-RPC response to stdout. You should see the four tool names listed.

---

## Available Tools

Every tool's input is validated by a Zod schema; the SDK applies the schema before the handler runs.

### `minimax_chat`

Non-streaming chat completion. Returns the assistant content plus optional usage.

**Input:**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `model` | string | no (default: `MiniMax-M3`) | |
| `messages` | array | yes | At least one message. Each has `role` (`system`/`user`/`assistant`/`tool`/`function`), `content`, and optional `name` / `tool_call_id`. |
| `temperature` | number | no | `[0, 2]`. |
| `top_p` | number | no | `[0, 1]`. |
| `n` | integer | no | Number of completions. |
| `max_tokens` | integer | no | ≤ 1,000,000 (hard cap). |
| `stop` | string \| string[] | no | |
| `presence_penalty` | number | no | `[-2, 2]`. |
| `frequency_penalty` | number | no | `[-2, 2]`. |
| `user` | string | no | Upstream abuse-tracking identifier. |

**Output:** `{ content, finish_reason, model, usage? }`.

### `minimax_complete`

Single-turn text completion. Wraps `prompt` (plus optional `system`) into a one-message conversation.

**Input:**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `model` | string | no (default: `MiniMax-M3`) | |
| `prompt` | string | yes | Non-empty. |
| `system` | string | no | System message prepended before `prompt`. |
| _(rest)_ | _same as `minimax_chat`_ | | |

**Output:** `{ content, finish_reason, model, usage? }`.

### `minimax_tool_call`

M3-native tool-use passthrough. Forwards `tools` and `tool_choice` to the upstream **verbatim** — the server does not validate or transform the function definitions.

**Input:**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| _(same as `minimax_chat`)_ | | | |
| `tools` | array | **yes** | Non-empty. Each entry is the OpenAI tool object (e.g. `{ type: "function", function: { name, description, parameters } }`). |
| `tool_choice` | string \| object | no | Standard OpenAI forms: `"auto"`, `"none"`, `"required"`, or `{"type": "function", "function": {"name": "..."}}`. |

**Output:** `{ content, finish_reason, model, usage?, tool_calls?, tool_call_payload? }`. When `finish_reason === "tool_calls"`, `tool_call_payload` is a structured JSON block with the call list — it is the JSON-serialized text content the MCP client renders.

### `minimax_count_tokens`

Local token count using the `cl100k_base` BPE encoding (the same one OpenAI's `tiktoken` uses for GPT-3.5/4). Does **not** make an upstream call — entirely client-side.

**Input:**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `model` | string | no (default: `MiniMax-M3`) | Recorded in the result, not used for tokenization. |
| `messages` | array | yes | At least one message. |

**Output:** `{ total, model, encoding, per_message: [{ role, tokens }] }`. The counts are deterministic and match `gpt-tokenizer`'s `cl100k_base` encoding.

---

## Development

### Setup

```bash
git clone https://github.com/your-org/minimax-llm-mcp.git
cd minimax-llm-mcp
npm install
```

### Scripts

| Script | What it does |
| --- | --- |
| `npm run build` | Bundle `src/index.ts` to `dist/` via `tsup` (CJS + `.d.ts` + sourcemap). |
| `npm run dev` | Same as `build` but with `--watch`. |
| `npm run typecheck` | `tsc --noEmit` against `tsconfig.json`. |
| `npm test` | Run the `vitest` suite once. |
| `npm run test:watch` | `vitest --watch`. |
| `npm run coverage` | `vitest run --coverage` (v8 provider; writes HTML to `coverage/`). |

### Project layout

```
src/
├── index.ts                # CLI entry point (stdio)
├── server.ts               # MCP server: registers the four tools
├── client.ts               # MiniMax M3 HTTP client (auth, retry, error mapping)
├── config.ts               # Zod-validated env config
├── errors.ts               # McpError factory + ErrorCategory
├── tools/
│   ├── chat.ts             # minimax_chat
│   ├── complete.ts         # minimax_complete
│   ├── tool-call.ts        # minimax_tool_call
│   └── count-tokens.ts     # minimax_count_tokens
└── transports/
    ├── stdio.ts            # startStdioServer(config)
    └── sse.ts              # startSSEServer(config, options)

tests/                      # Mirror of src/, plus a top-level suite
                            # for the server, the HTTP client, the
                            # SSE transport, and the error helpers.
```

### TDD workflow

The slices were added in this order: scaffold → config → errors → client → count-tokens → chat → complete → tool-call → server → stdio → SSE. Each slice added the source file(s), the matching `tests/.../*.test.ts`, and was verified with `npm test` + `npm run coverage` before moving on. When adding a new tool or transport, follow the same pattern: write a failing test, write the engine, run the suite.

### Adding a new tool

1. Create `src/tools/<name>.ts` with a Zod input schema, an `X_INPUT_SCHEMA` export, and a `handleX(client, input, signal?)` function. The handler returns a typed result object; the SDK wraps it in `{ content: [{ type: "text", text: ... }] }`.
2. Create `tests/tools/<name>.test.ts` with `vi.fn()`-based client stubs.
3. Register the tool in `src/server.ts` via `server.registerTool(name, { description, inputSchema: X_INPUT_SCHEMA.shape }, async (args) => { ... })`.

### Adding a new env var

1. Add a Zod schema entry to `CONFIG_SCHEMA` in `src/config.ts` (with a default if optional).
2. Add the uncommented placeholder to `.env.example`.
3. Add tests in `tests/config.test.ts` covering the validation paths.

---

## Publishing

The package is `npm publish`-ready out of the box (the `bin` entry, `files` whitelist of `["dist"]`, `engines`, `main`, `types`, and `license` are all wired up). A pre-publish checklist:

1. Bump `version` in `package.json`.
2. `npm run typecheck` — clean.
3. `npm test` — 100% green; coverage ≥ 80% on `src/`.
4. `npm run build` — `dist/index.js` has the shebang and is executable.
5. `npm pack` — inspect the tarball. The `package` field should include only `dist/`, `package.json`, and `README.md`.
6. `npm publish --dry-run` — confirm the publish plan.
7. `npm login` (one-time).
8. `npm publish` — tag with `latest` for production releases.

The pre-publish step in CI should also `npm install` in a clean checkout and `npm test` to catch any drift between the test environment and the publish artifact.

---

## License

[MIT](./LICENSE) — Copyright (c) 2026 minimax-llm-mcp contributors.

See [`LICENSE`](./LICENSE) for the full text.

TDQS

A3.5/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a distinct purpose: chat completion, single-turn completion, local token counting, and tool-use passthrough. No overlap or confusion.

Naming Consistency5/5

All tools follow the consistent pattern 'minimax_<action>' with snake_case, making them predictable and easy to navigate.

Tool Count5/5

4 tools is well-scoped for an LLM server, covering the essential interactions without unnecessary bloat.

Completeness4/5

Covers core LLM operations but lacks streaming support, which is a common expectation for chat completions. Otherwise sufficient.

Maintenance

ActivityInactive
ResponsivenessNo issues