Skip to main content
Glama
igorgrv1

@igorromero/ciphersuite-mcp

by igorgrv1
README.md
# ciphersuite-mcp

An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that provides AES-256-CBC encryption and decryption tools, resources describing each algorithm, and ready-to-use prompts — all runnable directly inside VS Code Copilot Chat.

![tools](asset/tools.png)
---

## What it does

| Capability | Name | Description |
|---|---|---|
| 🔧 Tool | `encrypt_message` | Encrypts any plain-text message with a passphrase |
| 🔧 Tool | `decrypt_message` | Decrypts a previously encrypted message with the same passphrase |
| 📄 Resource | `encryption://info` | Returns details about the encryption algorithm, key derivation, and output format |
| 📄 Resource | `decryption://info` | Returns how to use the decrypt tool: expected format, passphrase rules, and common errors |
| 💬 Prompt | `encrypt_message_prompt` | Pre-built prompt that asks the agent to encrypt a message |
| 💬 Prompt | `decrypt_message_prompt` | Pre-built prompt that asks the agent to decrypt a message |

### How encryption works

- **Algorithm**: AES-256-CBC
- **Key derivation**: `scrypt(passphrase, fixedSalt, 32)` — you pass any passphrase string; the server derives a strong 32-byte key automatically
- **Output format**: `<IV in hex>:<ciphertext in hex>` — keep the full string to decrypt later
- **IV**: a fresh random 16-byte IV is generated on every encryption call, so the same message encrypted twice produces different output

---

## Prerequisites

- **Node.js v24+** (see `engines` in `package.json`)

---

## Installation

```bash
npm install
```

No build step is needed — the server runs TypeScript directly via Node.js native TypeScript support.

---

## Using in VS Code

### 1. Add the MCP server configuration

Create (or open) `.vscode/mcp.json` in your workspace and add:

```json
{
  "servers": {
    "ciphersuite-mcp": {
      "command": "node",
      "args": ["--experimental-strip-types", "ABSOLUTE_PATH_TO_PROJECT/src/index.ts"]
    }
  }
}
```

or via npm:

```json
{
  "servers": {
    "ciphersuite-mcp": {
      "command": "npx",
      "args": ["-y", "@igorromero/ciphersuite-mcp"]
    }
  }
}
```

> **Tip:** You can also add this server to your user-level MCP config at `~/.vscode/mcp.json` to make it available in every workspace.

### 2. Reload VS Code

Open the Command Palette (`Cmd+Shift+P`) and run **Developer: Reload Window** (or just restart VS Code).

### 3. Use it in Copilot Chat

Open Copilot Chat (Agent mode) and try:

```
Encrypt the message "Hello, World!" using the passphrase "my-secret-key"
```

```
Decrypt this message: a3f1...:<ciphertext> using the passphrase "my-secret-key"
```

```
Show me the encryption://info resource
```

The agent will automatically call the appropriate tool and return the result.

---

## Running the MCP Inspector

The MCP Inspector lets you explore and test all tools, resources, and prompts interactively in a browser UI:

```bash
npm run mcp:inspect
```

This opens the inspector at `http://localhost:5173` and connects it to the running server.

---

## Running tests

```bash
# Run all tests once
npm test

# Run tests in watch mode (with debugger)
npm run test:dev
```

The test suite covers:

- Encrypting a message
- Decrypting a message with the correct passphrase
- Listing and reading the `encryption://info` resource
- Fetching both prompts
- Error: decrypting with the wrong passphrase
- Error: decrypting a malformed ciphertext

---

## Project structure

```
src/
  index.ts   # Entry point — connects the server to stdio transport
  mcp.ts     # All tools, resources, and prompts are registered here
tests/
  mcp.test.ts
```

---

## Available scripts

| Script | Description |
|---|---|
| `npm start` | Start the server (used by MCP clients) |
| `npm run dev` | Start with file-watch and Node.js inspector |
| `npm test` | Run all tests |
| `npm run test:dev` | Run tests in watch mode |
| `npm run mcp:inspect` | Open the MCP Inspector UI |

---

---

## Creating from Scratch

This section documents how this MCP server was built step by step — useful for creating new MCP servers in the future.

### MCP Transport Types

There are 3 types of MCP transport:

| Type | Class | Description |
|---|---|---|
| `stdio` | `StdioServerTransport` | Runs locally on the machine — the most common for local tools |
| `http` | — | Runs as an API over HTTP |
| `sse` | — | Server-Sent Events — processes data on demand (streaming) |

### Dependencies

```json
// package.json
"dependencies": {
  "@modelcontextprotocol/sdk": "^1.27.1",
  "@types/node": "^24.11.0",
  "zod": "^3.25.76"
}
```

---

### 1. Entry Point — `src/index.ts`

The entry point creates a `StdioServerTransport` and connects the MCP server to it:

```typescript
// src/index.ts
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { server } from "./mcp.ts";

async function main() {
    const transport = new StdioServerTransport()
    await server.connect(transport)
    console.error('Encrypt MCP Server running on stdio')
}

main().catch((error) => {
    console.error("Fatal error in main():", error);
    process.exit(1);
});
```

---

### 2. Server Setup — `src/mcp.ts`

Create the MCP server instance with a name and version:

```typescript
// src/mcp.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

export const server = new McpServer({
    name: '@igorromero/ciphersuite-mcp',
    version: '0.0.1'
})
```

---

### 3. Registering Tools

![tools](asset/tools.png)

Tools are functions the LLM can call to perform actions. Use `server.registerTool`, which takes 3 arguments:

1. **Name** of the tool (string)
2. **Config object** containing:
   - `description` — what the tool does; the LLM uses this to decide when to call it
   - `inputSchema` — equivalent to the request body, defined with Zod
   - `outputSchema` — equivalent to the response body, defined with Zod
3. **Async handler function** — the actual implementation

```typescript
server.registerTool(
    'encrypt_message',
    {
        description: 'Encrypt a message',
        inputSchema: {
            message: z.string().describe("The message to encrypt"),
            encryptionKey: z.string().describe(
                "Any passphrase to use for encryption — the server derives a strong key from it automatically"
            )
        },
        outputSchema: {
            encryptedMessage: z.string().describe(
                "The encrypted message (format: iv:ciphertext)"
            )
        }
    },
    async ({ message, encryptionKey }) => {
        try {
            const encryptedMessage = encrypt(message, encryptionKey)
            return {
                content: [{ type: "text", text: encryptedMessage }],
                structuredContent: { encryptedMessage }
            }
        } catch (error) {
            return {
                isError: true,
                content: [{
                    type: 'text',
                    text: `Failed to encrypt message! Error: ${error instanceof Error ? error.message : String(error)}`
                }]
            }
        }
    }
)
```

The same pattern applies to `decrypt_message` — just swap the input/output schema fields and call `decrypt()` instead.

---

### 4. Registering Resources

![resource](asset/resource.png)

Resources provide static or computed information that helps the LLM understand the context around a tool. Use `server.registerResource`, which takes 4 arguments:

1. **Name** of the resource
2. **URI template** (usually the same as the name)
3. **Config object** containing a `description`
4. **Handler function** that returns `contents` — an array of objects with `uri`, `mimeType`, and `text`

```typescript
server.registerResource(
    'encryption://info',
    'encryption://info',
    {
        description: 'Describes the encryption algorithm, key requirements, and output format used by this server',
    },
    () => ({
        contents: [
            {
                uri: "encryption://info",
                mimeType: "text/plain",
                text: `
Algorithm : AES-256-CBC
Key derivation: scrypt (passphrase + fixed server salt → 32-byte key)
Output format: <16-byte IV in hex>:<ciphertext in hex>  (separated by ":")
Notes:
  - Users pass any passphrase — the server derives a strong 32-byte key automatically using scrypt.
  - A random IV is generated for every encryption — the same message encrypted twice will produce different output.
  - Use the exact same passphrase to decrypt.
  - Keep the full "iv:ciphertext" string to decrypt later.
                `.trim(),
            },
        ]
    })
)
```

The `decryption://info` resource follows the same pattern, describing the expected input format, passphrase requirements, and common error scenarios for the decrypt tool.

---

### 5. Registering Prompts

![prompt](asset/prompt.png)

Prompts are pre-built message templates that the LLM can use to invoke tools in a guided way. Use `server.registerPrompt`, which takes 3 arguments:

1. **Name** of the prompt
2. **Config object** containing:
   - `description` — what the prompt does
   - `argsSchema` — the input parameters, defined with Zod
3. **Handler function** that returns `messages` — an array of objects with `role` (`user` or `assistant`) and `content`

```typescript
server.registerPrompt(
    "encrypt_message_prompt",
    {
        description: "Prompt to encrypt a plain-text message using the encrypt_message tool",
        argsSchema: {
            message: z.string().describe("The message to encrypt"),
            encryptionKey: z.string().describe(
                "Any passphrase to use for encryption — the server derives a strong key from it automatically"
            )
        }
    },
    ({ message, encryptionKey }) => ({
        messages: [
            {
                role: 'user',
                content: {
                    type: "text",
                    text: `Please encrypt the following message using the encrypt_message tool.\nMessage: ${message}\nEncryption key: ${encryptionKey}`,
                }
            }
        ]
    })
)
```

The `decrypt_message_prompt` follows the same pattern — takes `encryptedMessage` and `encryptionKey` as args and instructs the LLM to call `decrypt_message`.

---

### 6. Connecting the MCP Server to an IDE

#### VS Code (automatic)

Create `.vscode/mcp.json` in the project root. VS Code will detect it automatically:

```json
{
    "servers": {
        "ciphersuite-mcp": {
            "command": "node",
            "args": [
                "--experimental-strip-types",
                "src/index.ts"
            ]
        }
    }
}
```

#### Other IDEs / Other Projects

Copy the `ciphersuite-mcp` server entry into the MCP config file of the target project or IDE. The server runs as a subprocess via `stdio`, so any MCP-compatible client can connect to it.

TDQS

A3.6/5.0

Scored across 2 tools

Disambiguation5/5

The two tools are perfectly distinct: one encrypts, the other decrypts. There is no overlap or potential for confusion between them.

Naming Consistency5/5

Both tools follow the same verb_noun pattern: encrypt_message and decrypt_message. Naming is consistent and intuitive.

Tool Count4/5

At only 2 tools, the server is minimal, but the domain of encryption/decryption inherently requires exactly these two operations. The count is slightly under the typical 3-15 range but perfectly appropriate for its narrow scope.

Completeness5/5

The tool surface covers the full encryption/decryption lifecycle with no missing operations. For a cipher suite, encrypt and decrypt are the only necessary functions.

Maintenance

ActivityMaintained
ResponsivenessNo issues