cysic-mcp
by nguyennpduoc
README.md
# cysic-mcp
A [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server in
TypeScript/Node.js that exposes the [Cysic AI](https://cysic.xyz) model
(`minimax-m3`) to any MCP client over **stdio**, with server-side session
memory and a tone-rewriting tool.
## Problem
AI assistants and other MCP clients need a uniform way to call an LLM, manage
multi-turn conversations across many requests, and rewrite text in different
tones — but most existing LLM SDKs are HTTP-first and require per-integration
boilerplate. A Cysic AI user has to:
1. Wire `axios` (or `fetch`) with the right `Authorization: Bearer …` header.
2. Implement retry/backoff for `429` / `5xx` / network failures.
3. Validate every tool input with `zod` so a misbehaving client cannot crash
the server.
4. Manage session state for multi-turn chat.
5. Plug the whole thing into their MCP client.
`cysic-mcp` collapses all five into a single `npm install` + one
configuration file.
## Solution
`cysic-mcp` is a single, small Node.js process that speaks the MCP
**stdio** transport. It registers exactly:
- **3 tools**: `cysic_chat`, `cysic_session`, `cysic_humanize`
- **2 resources**: `cysic://models`, `cysic://sessions/{id}`
- **2 prompts**: `code_review`, `humanize_text`
…and nothing more. The HTTP layer is a typed `CysicClient` wrapper around
`axios` with explicit timeout + exponential-backoff retries. All tool inputs
are validated by `zod`. All configuration is environment-driven; the server
fails fast at startup if the required `CYSIC_API_KEY` is missing.
## Feature Checklist
The shipped surface, one item per row:
- [x] **Tool: `cysic_chat { prompt, system?, temperature? }`** — single-turn
chat with the Cysic AI model. `prompt` is required (min length 1);
`system` overrides the default "You are a helpful assistant." preamble;
`temperature` is optional in `[0, 2]`.
- [x] **Tool: `cysic_session { session_id, message }`** — multi-turn chat
with **server-side history**. The server keeps the full conversation
per `session_id` in an in-process `Map`; each call sends the entire
history to the model and appends the assistant reply. Sessions are
created lazily on first use.
- [x] **Tool: `cysic_humanize { text, tone? }`** — rewrite `text` in a
requested tone. `tone` defaults to `neutral` and accepts
`neutral | friendly | formal | concise | confident`. Internally uses
a tone-specific system prompt and a low sampling temperature
(`0.2` for `concise`, `0.4` otherwise) to keep rewrites stable.
- [x] **Resource: `cysic://models`** — static catalog of models served by
this MCP server, rendered as a JSON `text/plain` payload
(e.g. `{ models: [{ id: "minimax-m3", provider: "cysic", default: true }] }`).
- [x] **Resource: `cysic://sessions/{id}`** — read template for a single
session's message history. The `{id}` path segment is the
`session_id`. Returns the JSON-serialized message array; missing
sessions return `[]`.
- [x] **Prompt: `code_review { language?, code }`** — render a senior-level
code-review prompt for a code snippet. `language` is optional
(e.g. `"TypeScript"`); `code` is required. The prompt returns two
messages the client can render as a chat template.
- [x] **Prompt: `humanize_text { tone?, text }`** — render a prompt that
mirrors the `cysic_humanize` tool contract so an MCP client can
present it as a chat template without invoking the tool.
**Distinctive features (innovation callouts):**
- [x] **Server-side session memory** — `cysic_session` keeps a per-id
`Map<string, Message[]>` so the client does not have to round-trip
history. History is in-process; sessions are lost on restart and
are not shared across processes (acceptable for a single-instance
stdio MCP server).
- [x] **`cysic_humanize` tone rewriting** — five built-in tones
(`neutral`, `friendly`, `formal`, `concise`, `confident`), each
with its own instruction string and temperature. The same tone
vocabulary is reused by the `humanize_text` prompt so the prompt
and the tool stay in lock-step.
## Architecture
```
+----------------------+ stdio JSON-RPC +------------------+
| MCP client (e.g. | <--------------------------> | cysic-mcp |
| Claude Desktop, | | (Node.js) |
| any stdio MCP host) | | |
+----------------------+ | src/server.ts |
| | |
| v |
| src/tools/ |
| src/resources/ |
| src/prompts/ |
| | |
| v |
| CysicClient |
| (axios + retry) |
+--------+---------+
|
v
POST /v1/chat/completions
https://token-ai.cysic.xyz
```
### Project layout
```
src/
server.ts # MCP server bootstrap, transport wiring
config.ts # env loading (loadConfig)
cysicClient.ts # axios wrapper + retry/backoff
state.ts # in-process session Map (sessions)
errors.ts # CysicError + toMcpErrorMessage sanitizer
tones.ts # shared tone vocabulary (used by tool + prompt)
mcpShims.ts # untyped SDK registration shims
tools/
index.ts # registerTools(server, client, state)
cysicChat.ts # cysic_chat tool
cysicSession.ts # cysic_session tool
cysicHumanize.ts # cysic_humanize tool
resources/
index.ts # registerResources(server, state)
models.ts # cysic://models resource
session.ts # cysic://sessions/{id} resource template
prompts/
index.ts # registerPrompts(server)
codeReview.ts # code_review prompt
humanizeText.ts # humanize_text prompt
tests/
cysicClient.test.ts # 8 tests (a-e per AC-3, plus a few extras)
structure.test.ts # 5 tests defending the AC-5 module split
dist/ # tsc output (gitignored)
```
## Install
`cysic-mcp` requires Node.js `>= 18.17` (for native `fetch` /
`AbortController`; we still use `axios` per the project rubric for
explicit timeout + retry control).
```bash
git clone <this-repo> cysic-mcp
cd cysic-mcp
npm install
npm run build
```
The build emits `dist/server.js`, which is the entry point referenced by
the `start` script and the `cysic-mcp` bin.
## Configure
Copy the example env file and fill in your real Cysic API key:
```bash
cp .env.example .env
# then edit .env and set CYSIC_API_KEY=...
```
The full set of environment variables:
| Variable | Required | Default | Description |
| -------------------- | -------- | ------------------------------------ | --------------------------------------------------------------------------- |
| `CYSIC_API_KEY` | **yes** | — | Bearer token for the Cysic AI endpoint. Never echoed in logs. |
| `CYSIC_BASE_URL` | no | `https://token-ai.cysic.xyz/v1` | Base URL for the Cysic AI OpenAI-compatible API. |
| `CYSIC_MODEL` | no | `minimax-m3` | Default model used by the tools when no per-call override is given. |
| `CYSIC_TIMEOUT_MS` | no | `30000` | Per-request HTTP timeout in milliseconds. |
| `CYSIC_MAX_RETRIES` | no | `3` | Retries on `429` / `5xx` / network errors (exponential backoff + jitter). |
If `CYSIC_API_KEY` is missing or empty, the server **fails fast at
startup** with a clear error message that names the variable (but does
not echo the value).
## Run
Production (after `npm run build`):
```bash
npm start
# equivalent to: node dist/server.js
```
Development (live TypeScript via `tsx`):
```bash
npm run dev
# equivalent to: tsx src/server.ts
```
The server uses the MCP **stdio** transport. There is no HTTP port to
scrape; `stdout` is reserved for the MCP JSON-RPC stream. All fatal
startup errors are written to `stderr` so they do not corrupt the MCP
channel.
## MCP Client Registration
Point your MCP client at the compiled entry point. The exact JSON
shape depends on the client; the two common forms are below.
### Claude Desktop (`claude_desktop_config.json`)
Add this entry under `mcpServers`:
```json
{
"mcpServers": {
"cysic": {
"command": "node",
"args": ["/absolute/path/to/cysic-mcp/dist/server.js"],
"env": {
"CYSIC_API_KEY": "sk-your-real-key-here"
}
}
}
}
```
If you prefer the `.env` file approach, omit the `env` block and let the
server read it from disk:
```json
{
"mcpServers": {
"cysic": {
"command": "node",
"args": ["/absolute/path/to/cysic-mcp/dist/server.js"]
}
}
}
```
### Generic stdio MCP config
Any MCP host that speaks stdio can launch the server as a child
process. The minimum invocation is:
```json
{
"command": "node",
"args": ["dist/server.js"],
"cwd": "/absolute/path/to/cysic-mcp",
"env": {
"CYSIC_API_KEY": "sk-your-real-key-here",
"CYSIC_BASE_URL": "https://token-ai.cysic.xyz/v1",
"CYSIC_MODEL": "minimax-m3"
}
}
```
For a development-mode build, swap `node` + `dist/server.js` for
`npx tsx src/server.ts` and the host will run the TypeScript source
directly.
## Example Tool Calls
The examples below are copy-pasteable. The exact wire format depends on
your MCP client; the JSON argument shape is what the server validates
with `zod`.
### `cysic_chat` — single-turn chat
```json
{
"name": "cysic_chat",
"arguments": {
"prompt": "What is the capital of France?",
"system": "You are a concise geography tutor.",
"temperature": 0.2
}
}
```
Returns:
```json
{
"content": [{ "type": "text", "text": "Paris." }],
"isError": false
}
```
### `cysic_session` — multi-turn chat with server-side history
Turn 1:
```json
{
"name": "cysic_session",
"arguments": {
"session_id": "user-42",
"message": "Hi, my name is Alice."
}
}
```
Turn 2 (the server sends the full `[user, assistant, user]` history to
the model):
```json
{
"name": "cysic_session",
"arguments": {
"session_id": "user-42",
"message": "What's my name?"
}
}
```
The full history can be inspected at runtime by reading the
`cysic://sessions/user-42` resource.
### `cysic_humanize` — tone rewriting
```json
{
"name": "cysic_humanize",
"arguments": {
"text": "We're gonna ship the thing on Friday. Probably.",
"tone": "formal"
}
}
```
`tone` defaults to `neutral`; the full set of accepted values is
`neutral | friendly | formal | concise | confident`.
### `cysic://models` — model catalog
```json
{ "uri": "cysic://models" }
```
Returns a JSON payload listing the Cysic AI models served by this
server (currently `{ id: "minimax-m3", provider: "cysic", default: true }`).
### `cysic://sessions/{id}` — per-session history
```json
{ "uri": "cysic://sessions/user-42" }
```
Returns the JSON-serialized message array for `user-42`. Missing
sessions return `[]`.
### `code_review` prompt
```json
{
"name": "code_review",
"arguments": {
"language": "TypeScript",
"code": "const x: number = 1; console.log(x);"
}
}
```
Renders two messages the client can show as a chat template: a
"you are a senior TypeScript engineer…" brief and a fenced
code block to review.
### `humanize_text` prompt
```json
{
"name": "humanize_text",
"arguments": {
"tone": "confident",
"text": "I think maybe we could try to ship this?"
}
}
```
Renders two messages that mirror the `cysic_humanize` tool contract
(the tone instruction as the first message, the text to rewrite as the
second). Because the MCP `PromptMessage.role` enum is restricted to
`"user" | "assistant"`, both messages are emitted with `role: "user"`;
the "system" intent from the plan is preserved by the
two-message shape, not by a literal `role: "system"`.
## Testing
Unit tests use [`vitest`](https://vitest.dev/) and stub the HTTP layer
with [`nock`](https://github.com/nock/nock) so no real network is ever
made.
```bash
npm test
```
The current suite covers:
- `tests/cysicClient.test.ts` — 8 tests across the AC-3 cases (a) 200
returns first choice, (b) 429 retried then succeeds, (c) 500 retried
then fails with no `apiKey` in the message, (d) 400 fails fast with
`code: BAD_REQUEST` and is not retried, (e) request body includes
`model` and `messages`, plus extras for temperature pass-through,
per-call model override, and empty-messages rejection.
- `tests/structure.test.ts` — 5 tests importing every per-feature
`registerX` function and the `CysicError` / `toMcpErrorMessage` /
`TONE_VALUES` / `temperatureForTone` / `TONE_INSTRUCTIONS` exports
from the **compiled** `dist/` tree, to defend the AC-5 module split.
## License
[MIT](./LICENSE)
TDQS
A3.6/5.0
Scored across 3 tools
Disambiguation5/5
Each tool has a distinctly clear purpose: single-turn chat, text rewriting, and multi-turn chat. No overlap or ambiguity.
Naming Consistency3/5
All tools use the 'cysic_' prefix, but the second part mixes verb forms ('chat', 'humanize') with a noun ('session'), creating a slight inconsistency in naming pattern.
Tool Count4/5
Three tools is reasonable for a focused AI interaction server, though slightly minimal. It covers essential functions without unnecessary bloat.
Completeness4/5
The tool set covers the core use cases (single/multi-turn chat and text rewriting). Missing features like session management or system prompt configuration are minor gaps.
Maintenance
ActivityInactive
ResponsivenessNo issues