Skip to main content
Glama
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