# Directory Structure
```
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── create-ad.js
├── DEMO-VISTA.md
├── EPIC-UNICODE-ART.md
├── examples
│ ├── all-templates.js
│ ├── basic-usage.js
│ └── mcp-client.js
├── LICENSE
├── mcp.json
├── package-lock.json
├── package.json
├── README.md
├── src
│ ├── integrations
│ │ └── symbl.js
│ ├── server
│ │ └── index.js
│ ├── steganography
│ │ └── manager.js
│ └── templates
│ └── engine.js
├── test-puzzle.js
├── test-simple.js
└── VISUAL-DEMO.md
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | node_modules/
2 | *.log
3 | *.tmp
4 | .DS_Store
5 | .env
6 | dist/
7 | build/
8 | coverage/
9 | .vscode/
10 | .idea/
11 | *.swp
12 | *.swo
13 | *~
14 |
```
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
```
1 | # Development files
2 | test-puzzle.js
3 | DEMO-VISTA.md
4 | VISUAL-DEMO.md
5 | *.test.js
6 | *.spec.js
7 |
8 | # Config files
9 | .eslintrc*
10 | .prettierrc*
11 | jest.config.js
12 |
13 | # Dev dependencies
14 | node_modules/
15 | coverage/
16 | .nyc_output/
17 |
18 | # Editor files
19 | .vscode/
20 | .idea/
21 | *.swp
22 | *.swo
23 | *~
24 |
25 | # OS files
26 | .DS_Store
27 | Thumbs.db
28 |
29 | # Git
30 | .git/
31 | .gitignore
32 |
33 | # Logs
34 | *.log
35 | npm-debug.log*
36 |
37 | # Examples (they're in README)
38 | # Keep examples/ for users to reference!
39 |
40 | # CI/CD
41 | .github/
42 | .travis.yml
43 | .circleci/
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # 🌌 Unicode Puzzles MCP
2 |
3 | [](https://www.npmjs.com/package/unicode-puzzles-mcp)
4 | [](https://opensource.org/licenses/MIT)
5 | [](https://modelcontextprotocol.io)
6 |
7 | Quantum steganography puzzles powered by MCP. Create and manage encoded messages using zero-width characters and advanced Unicode techniques.
8 |
9 | ## 🎯 Features
10 |
11 | - Advanced Unicode steganography (zero-width characters, combining marks)
12 | - Quantum-themed puzzle templates
13 | - symbl.cc integration for character discovery
14 | - Full MCP memory system integration
15 | - Multiple encoding patterns and difficulty levels
16 | - Real-time puzzle generation and verification
17 |
18 | ## 🛠️ Installation
19 |
20 | ```bash
21 | npm install unicode-puzzles-mcp
22 | ```
23 |
24 | ## 💫 Quick Start
25 |
26 | ```javascript
27 | import { UnicodePuzzlesMCP } from 'unicode-puzzles-mcp';
28 |
29 | // Initialize MCP server
30 | const mcp = new UnicodePuzzlesMCP();
31 |
32 | // Create a quantum puzzle
33 | const puzzle = await mcp.createPuzzle({
34 | template: 'quantum',
35 | message: 'System integrity compromised',
36 | secret: 'LIBRAXIS://repair-protocol-7A'
37 | });
38 |
39 | // Decode an encoded message
40 | const decoded = await mcp.decodePuzzle(encodedText);
41 | ```
42 |
43 | ## 🌟 Puzzle Templates
44 |
45 | - **Quantum** - Messages encoded using quantum superposition principles
46 | - **Orbital** - Circular pattern encoding using orbital mechanics
47 | - **Glitch** - Random noise patterns with hidden data
48 | - **Void** - Space-based encoding using astronomical symbols
49 |
50 | ## 🔮 Examples
51 |
52 | ### Creating a Quantum Puzzle
53 | ```javascript
54 | const quantumPuzzle = await mcp.createPuzzle({
55 | template: 'quantum',
56 | message: 'Reality distortion detected',
57 | secret: 'Coordinates: α-359-ω',
58 | difficulty: 'advanced'
59 | });
60 |
61 | // Result: 【𝚀𝚄𝙰𝙽𝚃𝚄𝙼】∎∎∎Reality⠀distortion⠀detected∎∎∎
62 | // (with hidden ZWSP characters encoding the secret)
```
--------------------------------------------------------------------------------
/mcp.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "unicode-puzzles-mcp",
3 | "version": "0.1.0",
4 | "description": "MCP server for quantum steganography puzzles using Unicode",
5 | "author": "MCP Division",
6 | "license": "MIT",
7 | "main": "src/server/index.js",
8 | "mcp": {
9 | "serverType": "stdio",
10 | "runtime": "node",
11 | "command": "node",
12 | "args": ["src/server/index.js"]
13 | },
14 | "capabilities": {
15 | "tools": [
16 | "create_puzzle",
17 | "decode_puzzle",
18 | "list_templates",
19 | "encode_message",
20 | "decode_message",
21 | "search_characters",
22 | "get_zero_width_chars"
23 | ]
24 | }
25 | }
```
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
1 | # Changelog
2 |
3 | All notable changes to unicode-puzzles-mcp will be documented in this file.
4 |
5 | ## [0.1.0] - 2025-07-21
6 |
7 | ### 🎉 Initial Release
8 |
9 | #### Features
10 | - 🌟 Four unique puzzle templates: Quantum, Orbital, Glitch, and Void
11 | - 🔐 Binary, trinary, and random encoding methods
12 | - 🎚️ Three difficulty levels (easy, medium, hard)
13 | - 🔍 Integration with symbl.cc for Unicode character discovery
14 | - 🛠️ Full MCP (Model Context Protocol) server implementation
15 | - 📚 Comprehensive examples and documentation
16 |
17 | #### Templates
18 | - **Quantum**: Superposition-based encoding with quantum symbols
19 | - **Orbital**: Circular patterns with orbital mechanics
20 | - **Glitch**: Visual noise patterns for obfuscation
21 | - **Void**: Space-themed constellation encoding
22 |
23 | #### Zero-Width Characters Support
24 | - ZWSP (U+200B) - Zero Width Space
25 | - ZWNJ (U+200C) - Zero Width Non-Joiner
26 | - ZWJ (U+200D) - Zero Width Joiner
27 | - And 9 more invisible Unicode characters!
28 |
29 | ---
30 |
31 | Made with 🧠 by LibraxisAI MCP Division
```
--------------------------------------------------------------------------------
/test-simple.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 | /**
3 | * Simple test to verify the package works
4 | */
5 |
6 | import { StegoPuzzleManager } from './src/steganography/manager.js';
7 | import { TemplateEngine } from './src/templates/engine.js';
8 | import { SymblConnector } from './src/integrations/symbl.js';
9 |
10 | console.log('🧪 Running package verification tests...\n');
11 |
12 | try {
13 | // Test 1: Can create instances
14 | console.log('✓ StegoPuzzleManager instantiated');
15 | const manager = new StegoPuzzleManager();
16 |
17 | console.log('✓ TemplateEngine instantiated');
18 | const templates = new TemplateEngine();
19 |
20 | console.log('✓ SymblConnector instantiated');
21 | const symbl = new SymblConnector();
22 |
23 | // Test 2: Basic encoding works
24 | const encoded = await manager.encodeSecret('test', 'secret', {
25 | pattern: 'binary',
26 | difficulty: 'easy'
27 | });
28 | console.log('✓ Binary encoding works');
29 |
30 | // Test 3: Templates are available
31 | const templateList = templates.listTemplates();
32 | console.log(`✓ Found ${templateList.length} templates`);
33 |
34 | // Test 4: Zero-width characters defined
35 | const zwCount = Object.keys(manager.zeroWidthChars).length;
36 | console.log(`✓ ${zwCount} zero-width characters available`);
37 |
38 | console.log('\n✅ All tests passed! Package is ready for publishing.');
39 | process.exit(0);
40 |
41 | } catch (error) {
42 | console.error('\n❌ Test failed:', error.message);
43 | process.exit(1);
44 | }
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "unicode-puzzles-mcp",
3 | "version": "0.1.0",
4 | "description": "MCP server for quantum steganography puzzles using Unicode - hide secrets in plain sight using zero-width characters",
5 | "main": "src/server/index.js",
6 | "type": "module",
7 | "bin": {
8 | "unicode-puzzles-mcp": "./src/server/index.js"
9 | },
10 | "scripts": {
11 | "start": "node src/server/index.js",
12 | "dev": "nodemon src/server/index.js",
13 | "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
14 | "test:local": "node test-puzzle.js",
15 | "lint": "eslint src/",
16 | "prepublishOnly": "node test-simple.js"
17 | },
18 | "dependencies": {
19 | "@modelcontextprotocol/sdk": "^0.6.0",
20 | "jsdom": "^23.0.0",
21 | "node-fetch": "^3.3.2"
22 | },
23 | "devDependencies": {
24 | "jest": "^27.0.6",
25 | "nodemon": "^2.0.12",
26 | "eslint": "^7.32.0",
27 | "prettier": "^2.3.2"
28 | },
29 | "keywords": [
30 | "mcp",
31 | "model-context-protocol",
32 | "unicode",
33 | "steganography",
34 | "puzzle",
35 | "quantum",
36 | "zero-width",
37 | "hidden-messages",
38 | "encoding",
39 | "claude-desktop",
40 | "libraxis"
41 | ],
42 | "author": "Maciej Gad <[email protected]>",
43 | "license": "MIT",
44 | "repository": {
45 | "type": "git",
46 | "url": "git+https://github.com/maciejgad/unicode-puzzles-mcp.git"
47 | },
48 | "bugs": {
49 | "url": "https://github.com/maciejgad/unicode-puzzles-mcp/issues"
50 | },
51 | "homepage": "https://github.com/maciejgad/unicode-puzzles-mcp#readme",
52 | "engines": {
53 | "node": ">=18.0.0"
54 | },
55 | "files": [
56 | "src/",
57 | "LICENSE",
58 | "README.md",
59 | "mcp.json"
60 | ]
61 | }
```
--------------------------------------------------------------------------------
/examples/basic-usage.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 | /**
3 | * Basic usage example for unicode-puzzles-mcp
4 | * This example shows how to use the steganography functions directly
5 | */
6 |
7 | import { StegoPuzzleManager } from '../src/steganography/manager.js';
8 | import { TemplateEngine } from '../src/templates/engine.js';
9 |
10 | async function main() {
11 | const manager = new StegoPuzzleManager();
12 | const templates = new TemplateEngine();
13 |
14 | console.log('🌌 Unicode Puzzles - Basic Usage Example\n');
15 |
16 | // Example 1: Simple message encoding
17 | console.log('1. Simple Binary Encoding:');
18 | const encoded = await manager.encodeSecret(
19 | 'Hello World',
20 | 'secret123',
21 | { pattern: 'binary', difficulty: 'easy' }
22 | );
23 | console.log('Visible text:', encoded);
24 | console.log('Looks normal but contains hidden data!\n');
25 |
26 | // Example 2: Create a Quantum Puzzle
27 | console.log('2. Quantum Puzzle:');
28 | const quantumTemplate = templates.getTemplate('quantum', 'medium');
29 | const puzzle = await manager.createPuzzle({
30 | template: quantumTemplate,
31 | message: 'Meeting at midnight',
32 | secret: 'Location: 40.7128,-74.0060',
33 | difficulty: 'medium'
34 | });
35 | console.log('Puzzle:', puzzle);
36 |
37 | // Example 3: Decode the puzzle
38 | console.log('\n3. Decoding the Puzzle:');
39 | const decoded = await manager.decodePuzzle(puzzle);
40 | console.log('Visible message:', decoded.visibleText);
41 | console.log('Hidden secret:', decoded.hiddenMessage);
42 |
43 | // Example 4: Show zero-width character usage
44 | console.log('\n4. Zero-Width Character Analysis:');
45 | const zwChars = encoded.match(/[\u200B-\u200F\u2060-\u206F]/g);
46 | console.log('Total zero-width characters:', zwChars ? zwChars.length : 0);
47 | console.log('These are completely invisible to users!');
48 | }
49 |
50 | main().catch(console.error);
```
--------------------------------------------------------------------------------
/test-puzzle.js:
--------------------------------------------------------------------------------
```javascript
1 | import { StegoPuzzleManager } from './src/steganography/manager.js';
2 | import { TemplateEngine } from './src/templates/engine.js';
3 |
4 | // Test the puzzle creation locally
5 | async function testPuzzle() {
6 | const manager = new StegoPuzzleManager();
7 | const templates = new TemplateEngine();
8 |
9 | console.log('🧪 Testing Unicode Puzzles...\n');
10 |
11 | // Test 1: Create a quantum puzzle
12 | console.log('1️⃣ Creating Quantum Puzzle...');
13 | const quantumTemplate = templates.getTemplate('quantum', 'medium');
14 | const quantumPuzzle = await manager.createPuzzle({
15 | template: quantumTemplate,
16 | message: 'Reality distortion detected',
17 | secret: 'LIBRAXIS://repair-protocol-7A',
18 | difficulty: 'medium'
19 | });
20 | console.log('Result:', quantumPuzzle);
21 | console.log('');
22 |
23 | // Test 2: Decode the puzzle
24 | console.log('2️⃣ Decoding Quantum Puzzle...');
25 | const decoded = await manager.decodePuzzle(quantumPuzzle);
26 | console.log('Decoded:', decoded);
27 | console.log('');
28 |
29 | // Test 3: Simple binary encoding
30 | console.log('3️⃣ Testing Binary Encoding...');
31 | const binaryEncoded = await manager.encodeSecret(
32 | 'Hello World',
33 | 'Secret123',
34 | { pattern: 'binary', difficulty: 'easy' }
35 | );
36 | console.log('Binary Encoded:', binaryEncoded);
37 | console.log('Length:', binaryEncoded.length);
38 |
39 | // Count zero-width characters
40 | const zwChars = binaryEncoded.match(/[\u200B-\u200F\u2060-\u206F]/g);
41 | console.log('Zero-width characters:', zwChars ? zwChars.length : 0);
42 | console.log('');
43 |
44 | // Test 4: List all templates
45 | console.log('4️⃣ Available Templates:');
46 | const templateList = templates.listTemplates();
47 | templateList.forEach(t => {
48 | console.log(`- ${t.name}: ${t.description}`);
49 | console.log(` Difficulties: ${t.difficulties.join(', ')}`);
50 | });
51 | }
52 |
53 | // Run tests
54 | testPuzzle().catch(console.error);
```
--------------------------------------------------------------------------------
/examples/all-templates.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 | /**
3 | * Showcase all available puzzle templates
4 | */
5 |
6 | import { StegoPuzzleManager } from '../src/steganography/manager.js';
7 | import { TemplateEngine } from '../src/templates/engine.js';
8 |
9 | async function showcaseTemplates() {
10 | const manager = new StegoPuzzleManager();
11 | const templates = new TemplateEngine();
12 |
13 | console.log('🎨 Unicode Puzzles - Template Showcase\n');
14 | console.log('Each template creates unique visual patterns while hiding your secrets!\n');
15 |
16 | const secretMessage = 'LIBRAXIS-2025';
17 | const visibleMessage = 'The future is distributed';
18 |
19 | // Get all template names
20 | const templateList = templates.listTemplates();
21 |
22 | for (const templateInfo of templateList) {
23 | console.log(`\n${'='.repeat(60)}`);
24 | console.log(`📦 Template: ${templateInfo.name.toUpperCase()}`);
25 | console.log(`📝 ${templateInfo.description}`);
26 | console.log(`🎚️ Difficulties: ${templateInfo.difficulties.join(', ')}`);
27 | console.log(`${'='.repeat(60)}\n`);
28 |
29 | // Show each difficulty level
30 | for (const difficulty of templateInfo.difficulties) {
31 | const template = templates.getTemplate(templateInfo.name, difficulty);
32 | const puzzle = await manager.createPuzzle({
33 | template,
34 | message: visibleMessage,
35 | secret: secretMessage,
36 | difficulty
37 | });
38 |
39 | console.log(`[${difficulty.toUpperCase()}]:`);
40 | console.log(puzzle);
41 |
42 | // Analyze the encoding
43 | const hiddenChars = puzzle.match(/[\u200B-\u200F\u2060-\u206F]/g);
44 | console.log(`Hidden characters: ${hiddenChars ? hiddenChars.length : 0}`);
45 | console.log('');
46 | }
47 | }
48 |
49 | console.log('\n💡 TIP: Copy any puzzle above and it will maintain the hidden message!');
50 | console.log('The zero-width characters are preserved in clipboard operations.');
51 | }
52 |
53 | showcaseTemplates().catch(console.error);
```
--------------------------------------------------------------------------------
/examples/mcp-client.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 | /**
3 | * Example of using unicode-puzzles-mcp as an MCP client
4 | * This shows how Claude Desktop or other MCP clients would interact
5 | */
6 |
7 | import { Client } from '@modelcontextprotocol/sdk/client/index.js';
8 | import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
9 | import { spawn } from 'child_process';
10 |
11 | async function main() {
12 | console.log('🔌 Connecting to Unicode Puzzles MCP Server...\n');
13 |
14 | // Spawn the MCP server
15 | const serverProcess = spawn('node', ['../src/server/index.js'], {
16 | stdio: ['pipe', 'pipe', 'pipe']
17 | });
18 |
19 | // Create transport and client
20 | const transport = new StdioClientTransport({
21 | stdin: serverProcess.stdout,
22 | stdout: serverProcess.stdin,
23 | stderr: serverProcess.stderr
24 | });
25 |
26 | const client = new Client({
27 | name: 'unicode-puzzles-client',
28 | version: '1.0.0'
29 | }, {
30 | capabilities: {}
31 | });
32 |
33 | // Connect to server
34 | await client.connect(transport);
35 | console.log('✅ Connected to MCP server!\n');
36 |
37 | // List available tools
38 | console.log('📋 Available Tools:');
39 | const tools = await client.listTools();
40 | tools.tools.forEach(tool => {
41 | console.log(`- ${tool.name}: ${tool.description}`);
42 | });
43 |
44 | // Example 1: Create a puzzle
45 | console.log('\n🎯 Creating a Quantum Puzzle...');
46 | const puzzleResult = await client.callTool('create_puzzle', {
47 | template: 'quantum',
48 | message: 'System breach detected',
49 | secret: 'Access code: DELTA-7-ALPHA',
50 | difficulty: 'hard'
51 | });
52 |
53 | const puzzleData = JSON.parse(puzzleResult.content[0].text);
54 | console.log('Created:', puzzleData.puzzle);
55 |
56 | // Example 2: List templates
57 | console.log('\n📚 Available Templates:');
58 | const templatesResult = await client.callTool('list_templates', {});
59 | const templates = JSON.parse(templatesResult.content[0].text);
60 | console.log(templates);
61 |
62 | // Example 3: Search for zero-width characters
63 | console.log('\n🔍 Zero-Width Characters:');
64 | const zwResult = await client.callTool('get_zero_width_chars', {});
65 | const zwChars = JSON.parse(zwResult.content[0].text);
66 | console.log('Found', zwChars.chars.length, 'zero-width characters');
67 |
68 | // Cleanup
69 | await client.close();
70 | serverProcess.kill();
71 | console.log('\n👋 Disconnected from server');
72 | }
73 |
74 | main().catch(console.error);
```
--------------------------------------------------------------------------------
/src/integrations/symbl.js:
--------------------------------------------------------------------------------
```javascript
1 | import fetch from 'node-fetch';
2 | import jsdom from 'jsdom';
3 | const { JSDOM } = jsdom;
4 |
5 | export class SymblConnector {
6 | constructor() {
7 | this.baseUrl = 'https://symbl.cc/en';
8 | this.cache = new Map();
9 | this.rateLimit = {
10 | requests: 0,
11 | lastReset: Date.now(),
12 | limit: 100 // requests per minute
13 | };
14 |
15 | // Initialize character categories
16 | this.categories = {
17 | zeroWidth: [
18 | '200B', '200C', '200D', '200E', '200F',
19 | '2060', '206A', '206B', '206C', '206D',
20 | '206E', '206F'
21 | ],
22 | quantum: [
23 | '0278', '0299', '1D487', '1D688', '1D689',
24 | '1D68A', '1D68B', '1D68C', '1D68D', '1D68E'
25 | ],
26 | special: [
27 | '2022', '2023', '25A0', '25A1', '25CF',
28 | '25CB', '25D0', '25D1', '25D2', '25D3'
29 | ]
30 | };
31 | }
32 |
33 | async searchCharacters(query, category = null) {
34 | await this.checkRateLimit();
35 |
36 | // Check cache first
37 | const cacheKey = `search:${query}:${category}`;
38 | if(this.cache.has(cacheKey)) {
39 | return this.cache.get(cacheKey);
40 | }
41 |
42 | try {
43 | // Fetch search results from symbl.cc
44 | const response = await fetch(`${this.baseUrl}/search/?q=${encodeURIComponent(query)}`);
45 | const html = await response.text();
46 |
47 | // Parse results using jsdom
48 | const dom = new JSDOM(html);
49 | const document = dom.window.document;
50 |
51 | // Extract character data
52 | const results = Array.from(document.querySelectorAll('.char-block')).map(block => {
53 | const char = block.querySelector('.char').textContent;
54 | const code = block.querySelector('.code').textContent;
55 | const name = block.querySelector('.name').textContent;
56 |
57 | return {
58 | char,
59 | code: code.replace('U+', ''),
60 | name,
61 | category: this.determineCategory(code)
62 | };
63 | });
64 |
65 | // Filter by category if specified
66 | const filtered = category ?
67 | results.filter(r => r.category === category) :
68 | results;
69 |
70 | // Cache results
71 | this.cache.set(cacheKey, filtered);
72 |
73 | // Update rate limit counter
74 | this.rateLimit.requests++;
75 |
76 | return filtered;
77 |
78 | } catch (error) {
79 | throw new Error(`Failed to search symbl.cc: ${error.message}`);
80 | }
81 | }
82 |
83 | async getZeroWidthCharacters() {
84 | return this.getCharactersByCategory('zeroWidth');
85 | }
86 |
87 | async getQuantumCharacters() {
88 | return this.getCharactersByCategory('quantum');
89 | }
90 |
91 | async getSpecialCharacters() {
92 | return this.getCharactersByCategory('special');
93 | }
94 |
95 | async getCharactersByCategory(category) {
96 | if (!this.categories[category]) {
97 | throw new Error(`Invalid category: ${category}`);
98 | }
99 |
100 | const results = [];
101 | for (const code of this.categories[category]) {
102 | const char = await this.getCharacterByCode(code);
103 | if (char) results.push(char);
104 | }
105 |
106 | return results;
107 | }
108 |
109 | async getCharacterByCode(code) {
110 | await this.checkRateLimit();
111 |
112 | // Check cache
113 | const cacheKey = `char:${code}`;
114 | if(this.cache.has(cacheKey)) {
115 | return this.cache.get(cacheKey);
116 | }
117 |
118 | try {
119 | const response = await fetch(`${this.baseUrl}/${code}/`);
120 | const html = await response.text();
121 |
122 | const dom = new JSDOM(html);
123 | const document = dom.window.document;
124 |
125 | const charBlock = document.querySelector('.char-block');
126 | if (!charBlock) return null;
127 |
128 | const result = {
129 | char: charBlock.querySelector('.char').textContent,
130 | code,
131 | name: charBlock.querySelector('.name').textContent,
132 | category: this.determineCategory(code)
133 | };
134 |
135 | // Cache result
136 | this.cache.set(cacheKey, result);
137 |
138 | // Update rate limit
139 | this.rateLimit.requests++;
140 |
141 | return result;
142 |
143 | } catch (error) {
144 | throw new Error(`Failed to fetch character ${code}: ${error.message}`);
145 | }
146 | }
147 |
148 | determineCategory(code) {
149 | code = code.replace('U+', '');
150 |
151 | if(this.categories.zeroWidth.includes(code)) return 'zeroWidth';
152 | if(this.categories.quantum.includes(code)) return 'quantum';
153 | if(this.categories.special.includes(code)) return 'special';
154 | return 'other';
155 | }
156 |
157 | async checkRateLimit() {
158 | const now = Date.now();
159 | const timeSinceReset = now - this.rateLimit.lastReset;
160 |
161 | // Reset counter if a minute has passed
162 | if(timeSinceReset >= 60000) {
163 | this.rateLimit.requests = 0;
164 | this.rateLimit.lastReset = now;
165 | return;
166 | }
167 |
168 | // Check if we've hit the limit
169 | if(this.rateLimit.requests >= this.rateLimit.limit) {
170 | const waitTime = 60000 - timeSinceReset;
171 | throw new Error(`Rate limit exceeded. Please wait ${Math.ceil(waitTime/1000)} seconds.`);
172 | }
173 | }
174 |
175 | // Memory integration methods
176 | async saveToMemory(mcpMemory) {
177 | const timestamp = new Date().toISOString();
178 |
179 | // Store cache statistics
180 | await mcpMemory.create_entities([{
181 | name: `symbl_cache_${timestamp}`,
182 | entityType: 'Cache',
183 | observations: [
184 | `Total Cached Items: ${this.cache.size}`,
185 | `Rate Limit Status: ${this.rateLimit.requests}/${this.rateLimit.limit}`,
186 | `Last Reset: ${new Date(this.rateLimit.lastReset).toISOString()}`
187 | ]
188 | }]);
189 |
190 | // Store category statistics
191 | for(const [category, codes] of Object.entries(this.categories)) {
192 | await mcpMemory.create_entities([{
193 | name: `symbl_category_${category}_${timestamp}`,
194 | entityType: 'CharacterCategory',
195 | observations: [
196 | `Category: ${category}`,
197 | `Total Characters: ${codes.length}`,
198 | `Codes: ${codes.join(', ')}`
199 | ]
200 | }]);
201 | }
202 | }
203 |
204 | clearCache() {
205 | this.cache.clear();
206 | this.rateLimit.requests = 0;
207 | this.rateLimit.lastReset = Date.now();
208 | }
209 | }
```
--------------------------------------------------------------------------------
/src/steganography/manager.js:
--------------------------------------------------------------------------------
```javascript
1 | export class StegoPuzzleManager {
2 | constructor() {
3 | this.zeroWidthChars = {
4 | ZWSP: '\u200B', // Zero width space
5 | ZWNJ: '\u200C', // Zero width non-joiner
6 | ZWJ: '\u200D', // Zero width joiner
7 | LRM: '\u200E', // Left-to-right mark
8 | RLM: '\u200F', // Right-to-left mark
9 | WJ: '\u2060', // Word joiner
10 | ISS: '\u206A', // Inhibit symmetric swapping
11 | ASS: '\u206B', // Activate symmetric swapping
12 | IAFS: '\u206C', // Inhibit arabic form shaping
13 | AAFS: '\u206D', // Activate arabic form shaping
14 | NADS: '\u206E', // National digit shapes
15 | NODS: '\u206F' // Nominal digit shapes
16 | };
17 |
18 | this.patterns = {
19 | quantum: {
20 | prefix: '【𝚀𝚄𝙰𝙽𝚃𝚄𝙼】',
21 | separator: '∎∎∎',
22 | encoding: 'binary'
23 | },
24 | orbital: {
25 | prefix: '◉',
26 | separator: '◯',
27 | encoding: 'trinary'
28 | },
29 | glitch: {
30 | prefix: '[ERR0R]',
31 | separator: '',
32 | encoding: 'random'
33 | }
34 | };
35 | }
36 |
37 | async createPuzzle({ template, message, secret, difficulty = 'medium' }) {
38 | // Get encoding pattern based on template and difficulty
39 | const pattern = this.patterns[template.name];
40 |
41 | // Encode secret message
42 | const encodedText = await this.encodeSecret(message, secret, {
43 | pattern: pattern.encoding,
44 | difficulty
45 | });
46 |
47 | // Apply template formatting
48 | return this.applyTemplate(encodedText, template);
49 | }
50 |
51 | async encodeSecret(message, secret, options) {
52 | const { pattern, difficulty } = options;
53 |
54 | // Convert secret to binary
55 | const binarySecret = this.textToBinary(secret);
56 |
57 | // Initialize encoded text
58 | let encoded = '';
59 | let charIndex = 0;
60 |
61 | // Apply encoding based on pattern
62 | switch(pattern) {
63 | case 'binary':
64 | encoded = this.binaryEncode(message, binarySecret, difficulty);
65 | break;
66 | case 'trinary':
67 | encoded = this.trinaryEncode(message, binarySecret, difficulty);
68 | break;
69 | case 'random':
70 | encoded = this.randomEncode(message, binarySecret, difficulty);
71 | break;
72 | default:
73 | throw new Error('Invalid encoding pattern');
74 | }
75 |
76 | return encoded;
77 | }
78 |
79 | binaryEncode(message, binarySecret, difficulty) {
80 | let encoded = '';
81 | let secretIndex = 0;
82 |
83 | for(let i = 0; i < message.length; i++) {
84 | encoded += message[i];
85 |
86 | if(secretIndex < binarySecret.length) {
87 | // Insert zero-width character based on binary value
88 | if(binarySecret[secretIndex] === '1') {
89 | encoded += this.zeroWidthChars.ZWSP;
90 | } else {
91 | encoded += this.zeroWidthChars.ZWNJ;
92 | }
93 | secretIndex++;
94 | }
95 |
96 | // Add noise for higher difficulties
97 | if(difficulty === 'hard') {
98 | if(Math.random() > 0.7) {
99 | encoded += this.getRandomZeroWidth();
100 | }
101 | }
102 | }
103 |
104 | return encoded;
105 | }
106 |
107 | trinaryEncode(message, binarySecret, difficulty) {
108 | let encoded = '';
109 | let secretIndex = 0;
110 |
111 | // Convert binary to trinary for more complex encoding
112 | const trinarySecret = this.binaryToTrinary(binarySecret);
113 |
114 | for(let i = 0; i < message.length; i++) {
115 | encoded += message[i];
116 |
117 | if(secretIndex < trinarySecret.length) {
118 | // Use three zero-width characters for trinary encoding
119 | switch(trinarySecret[secretIndex]) {
120 | case '0':
121 | encoded += this.zeroWidthChars.ZWSP;
122 | break;
123 | case '1':
124 | encoded += this.zeroWidthChars.ZWNJ;
125 | break;
126 | case '2':
127 | encoded += this.zeroWidthChars.ZWJ;
128 | break;
129 | }
130 | secretIndex++;
131 | }
132 | }
133 |
134 | return encoded;
135 | }
136 |
137 | randomEncode(message, binarySecret, difficulty) {
138 | let encoded = '';
139 | let secretIndex = 0;
140 |
141 | const chars = Object.values(this.zeroWidthChars);
142 |
143 | for(let i = 0; i < message.length; i++) {
144 | encoded += message[i];
145 |
146 | // Random noise insertion
147 | if(Math.random() > 0.5) {
148 | encoded += chars[Math.floor(Math.random() * chars.length)];
149 | }
150 |
151 | // Secret encoding
152 | if(secretIndex < binarySecret.length) {
153 | if(binarySecret[secretIndex] === '1') {
154 | encoded += this.zeroWidthChars.WJ;
155 | }
156 | secretIndex++;
157 | }
158 | }
159 |
160 | return encoded;
161 | }
162 |
163 | textToBinary(text) {
164 | return text.split('').map(char =>
165 | char.charCodeAt(0).toString(2).padStart(8, '0')
166 | ).join('');
167 | }
168 |
169 | binaryToTrinary(binary) {
170 | // Convert binary to decimal
171 | const decimal = parseInt(binary, 2);
172 | // Convert decimal to trinary
173 | return decimal.toString(3);
174 | }
175 |
176 | getRandomZeroWidth() {
177 | const chars = Object.values(this.zeroWidthChars);
178 | return chars[Math.floor(Math.random() * chars.length)];
179 | }
180 |
181 | applyTemplate(encodedText, template) {
182 | return `${template.prefix}${template.separator}${encodedText}${template.separator}`;
183 | }
184 |
185 | // Decoding methods
186 | async decodePuzzle(encodedText) {
187 | // Detect template
188 | const template = this.detectTemplate(encodedText);
189 |
190 | // Remove template formatting
191 | const stripped = this.stripTemplate(encodedText, template);
192 |
193 | // Extract and decode hidden message
194 | return this.decodeSecret(stripped, template.encoding);
195 | }
196 |
197 | detectTemplate(encodedText) {
198 | for(const [name, pattern] of Object.entries(this.patterns)) {
199 | if(encodedText.startsWith(pattern.prefix)) {
200 | return { name, ...pattern };
201 | }
202 | }
203 | throw new Error('Unknown puzzle template');
204 | }
205 |
206 | stripTemplate(encodedText, template) {
207 | return encodedText
208 | .replace(template.prefix, '')
209 | .split(template.separator)[1];
210 | }
211 |
212 | decodeSecret(text, encoding) {
213 | let binary = '';
214 | let visibleText = '';
215 |
216 | for(let i = 0; i < text.length; i++) {
217 | const char = text[i];
218 |
219 | // Check if it's a zero-width character
220 | if(Object.values(this.zeroWidthChars).includes(char)) {
221 | switch(encoding) {
222 | case 'binary':
223 | binary += char === this.zeroWidthChars.ZWSP ? '1' : '0';
224 | break;
225 | case 'trinary':
226 | if(char === this.zeroWidthChars.ZWSP) binary += '0';
227 | else if(char === this.zeroWidthChars.ZWNJ) binary += '1';
228 | else if(char === this.zeroWidthChars.ZWJ) binary += '2';
229 | break;
230 | case 'random':
231 | if(char === this.zeroWidthChars.WJ) binary += '1';
232 | break;
233 | }
234 | } else {
235 | visibleText += char;
236 | }
237 | }
238 |
239 | return {
240 | visibleText,
241 | hiddenMessage: this.binaryToText(binary)
242 | };
243 | }
244 |
245 | binaryToText(binary) {
246 | // Split binary into 8-bit chunks
247 | const bytes = binary.match(/.{1,8}/g) || [];
248 |
249 | // Convert each byte to character
250 | return bytes.map(byte =>
251 | String.fromCharCode(parseInt(byte, 2))
252 | ).join('');
253 | }
254 | }
```
--------------------------------------------------------------------------------
/src/templates/engine.js:
--------------------------------------------------------------------------------
```javascript
1 | export class TemplateEngine {
2 | constructor() {
3 | this.templates = {
4 | quantum: {
5 | name: 'quantum',
6 | prefix: '【𝚀𝚄𝙰𝙽𝚃𝚄𝙼】',
7 | patterns: {
8 | easy: {
9 | opening: '∎',
10 | closing: '∎',
11 | separator: '⠀', // Unicode space
12 | noiseChance: 0.1
13 | },
14 | medium: {
15 | opening: '∎∎',
16 | closing: '∎∎',
17 | separator: '⠀',
18 | noiseChance: 0.3
19 | },
20 | hard: {
21 | opening: '∎∎∎',
22 | closing: '∎∎∎',
23 | separator: '⠀',
24 | noiseChance: 0.5
25 | }
26 | },
27 | quantumChars: ['α', 'β', 'γ', 'δ', 'ψ', 'Ψ', 'Φ', 'ℏ', '∞'],
28 | description: 'Quantum superposition encoding with variable noise'
29 | },
30 |
31 | orbital: {
32 | name: 'orbital',
33 | prefix: '◉',
34 | patterns: {
35 | easy: {
36 | opening: '◯',
37 | closing: '◯',
38 | separator: '·',
39 | rotationSteps: 4
40 | },
41 | medium: {
42 | opening: '◐',
43 | closing: '◑',
44 | separator: '∘',
45 | rotationSteps: 8
46 | },
47 | hard: {
48 | opening: '◒',
49 | closing: '◓',
50 | separator: '⋅',
51 | rotationSteps: 12
52 | }
53 | },
54 | orbitalChars: ['⌾', '☉', '⊕', '⊗', '⊙', '◎', '⚪', '⚫'],
55 | description: 'Circular pattern encoding with orbital mechanics'
56 | },
57 |
58 | glitch: {
59 | name: 'glitch',
60 | prefix: '[ERR0R]',
61 | patterns: {
62 | easy: {
63 | opening: '█',
64 | closing: '█',
65 | separator: ' ',
66 | glitchIntensity: 0.2
67 | },
68 | medium: {
69 | opening: '▓▒',
70 | closing: '▒▓',
71 | separator: '',
72 | glitchIntensity: 0.4
73 | },
74 | hard: {
75 | opening: '▓▒░',
76 | closing: '░▒▓',
77 | separator: '',
78 | glitchIntensity: 0.6
79 | }
80 | },
81 | glitchChars: ['░', '▒', '▓', '█', '☐', '☑', '☒', '✓', '✗'],
82 | description: 'Glitch-based encoding with visual noise'
83 | },
84 |
85 | void: {
86 | name: 'void',
87 | prefix: '✧・゚:*',
88 | patterns: {
89 | easy: {
90 | opening: '⋆',
91 | closing: '⋆',
92 | separator: ' ',
93 | constellationSize: 3
94 | },
95 | medium: {
96 | opening: '⋆⋆',
97 | closing: '⋆⋆',
98 | separator: '・',
99 | constellationSize: 5
100 | },
101 | hard: {
102 | opening: '⋆⋆⋆',
103 | closing: '⋆⋆⋆',
104 | separator: '⋆',
105 | constellationSize: 7
106 | }
107 | },
108 | spaceChars: ['✧', '✦', '★', '☆', '✯', '✩', '✫', '✬', '✭'],
109 | description: 'Space-themed encoding with constellation patterns'
110 | }
111 | };
112 | }
113 |
114 | getTemplate(name, difficulty = 'medium') {
115 | const template = this.templates[name];
116 | if (!template) {
117 | throw new Error(`Template '${name}' not found`);
118 | }
119 |
120 | const pattern = template.patterns[difficulty];
121 | if (!pattern) {
122 | throw new Error(`Difficulty '${difficulty}' not found for template '${name}'`);
123 | }
124 |
125 | return {
126 | ...template,
127 | pattern,
128 | difficulty
129 | };
130 | }
131 |
132 | generatePattern(template, length, options = {}) {
133 | const { name, pattern } = template;
134 | let result = '';
135 |
136 | switch (name) {
137 | case 'quantum':
138 | result = this.generateQuantumPattern(pattern, length, options);
139 | break;
140 | case 'orbital':
141 | result = this.generateOrbitalPattern(pattern, length, options);
142 | break;
143 | case 'glitch':
144 | result = this.generateGlitchPattern(pattern, length, options);
145 | break;
146 | case 'void':
147 | result = this.generateVoidPattern(pattern, length, options);
148 | break;
149 | default:
150 | throw new Error(`Unknown template type: ${name}`);
151 | }
152 |
153 | return result;
154 | }
155 |
156 | generateQuantumPattern(pattern, length, { seed = Math.random() } = {}) {
157 | let result = pattern.opening;
158 | const template = this.templates.quantum;
159 |
160 | for (let i = 0; i < length; i++) {
161 | // Add content character
162 | result += pattern.separator;
163 |
164 | // Add quantum noise based on difficulty
165 | if (Math.random() < pattern.noiseChance) {
166 | const quantumChar = template.quantumChars[
167 | Math.floor(Math.random() * template.quantumChars.length)
168 | ];
169 | result += quantumChar;
170 | }
171 | }
172 |
173 | return result + pattern.closing;
174 | }
175 |
176 | generateOrbitalPattern(pattern, length, { rotation = 0 } = {}) {
177 | let result = pattern.opening;
178 | const template = this.templates.orbital;
179 | const stepSize = (2 * Math.PI) / pattern.rotationSteps;
180 |
181 | for (let i = 0; i < length; i++) {
182 | // Calculate orbital position
183 | const angle = (rotation + i) * stepSize;
184 | const orbitalIndex = Math.floor((angle / (2 * Math.PI)) * template.orbitalChars.length);
185 | const orbitalChar = template.orbitalChars[orbitalIndex % template.orbitalChars.length];
186 |
187 | result += pattern.separator + orbitalChar;
188 | }
189 |
190 | return result + pattern.closing;
191 | }
192 |
193 | generateGlitchPattern(pattern, length, { intensity = 1.0 } = {}) {
194 | let result = pattern.opening;
195 | const template = this.templates.glitch;
196 | const effectiveIntensity = pattern.glitchIntensity * intensity;
197 |
198 | for (let i = 0; i < length; i++) {
199 | result += pattern.separator;
200 |
201 | // Add glitch artifacts based on intensity
202 | if (Math.random() < effectiveIntensity) {
203 | const glitchLength = Math.floor(Math.random() * 3) + 1;
204 | for (let j = 0; j < glitchLength; j++) {
205 | const glitchChar = template.glitchChars[
206 | Math.floor(Math.random() * template.glitchChars.length)
207 | ];
208 | result += glitchChar;
209 | }
210 | }
211 | }
212 |
213 | return result + pattern.closing;
214 | }
215 |
216 | generateVoidPattern(pattern, length, { constellation = [] } = {}) {
217 | let result = pattern.opening;
218 | const template = this.templates.void;
219 |
220 | // Create constellation pattern
221 | const constellationPoints = constellation.length > 0 ?
222 | constellation :
223 | this.generateConstellation(pattern.constellationSize);
224 |
225 | for (let i = 0; i < length; i++) {
226 | result += pattern.separator;
227 |
228 | // Add space characters at constellation points
229 | if (constellationPoints.includes(i)) {
230 | const spaceChar = template.spaceChars[
231 | Math.floor(Math.random() * template.spaceChars.length)
232 | ];
233 | result += spaceChar;
234 | }
235 | }
236 |
237 | return result + pattern.closing;
238 | }
239 |
240 | generateConstellation(size) {
241 | const points = new Set();
242 | while (points.size < size) {
243 | points.add(Math.floor(Math.random() * size * 2));
244 | }
245 | return Array.from(points).sort((a, b) => a - b);
246 | }
247 |
248 | // MCP Memory Integration
249 | async saveToMemory(mcpMemory) {
250 | const timestamp = new Date().toISOString();
251 |
252 | // Store template metadata
253 | for (const [name, template] of Object.entries(this.templates)) {
254 | await mcpMemory.create_entities([{
255 | name: `template_${name}_${timestamp}`,
256 | entityType: 'PuzzleTemplate',
257 | observations: [
258 | `Name: ${name}`,
259 | `Description: ${template.description}`,
260 | `Prefix: ${template.prefix}`,
261 | `Available Difficulties: ${Object.keys(template.patterns).join(', ')}`
262 | ]
263 | }]);
264 | }
265 |
266 | // Log template usage statistics if needed
267 | }
268 |
269 | listTemplates() {
270 | return Object.entries(this.templates).map(([name, template]) => ({
271 | name,
272 | description: template.description,
273 | difficulties: Object.keys(template.patterns)
274 | }));
275 | }
276 | }
```
--------------------------------------------------------------------------------
/src/server/index.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 |
3 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5 | import {
6 | CallToolRequestSchema,
7 | ListResourcesRequestSchema,
8 | ListToolsRequestSchema,
9 | ReadResourceRequestSchema,
10 | ErrorCode,
11 | McpError
12 | } from '@modelcontextprotocol/sdk/types.js';
13 | import { StegoPuzzleManager } from '../steganography/manager.js';
14 | import { SymblConnector } from '../integrations/symbl.js';
15 | import { TemplateEngine } from '../templates/engine.js';
16 |
17 | class UnicodePuzzlesMCP {
18 | constructor() {
19 | this.server = new Server(
20 | {
21 | name: 'unicode-puzzles-mcp',
22 | version: '0.1.0'
23 | },
24 | {
25 | capabilities: {
26 | tools: {},
27 | resources: {}
28 | }
29 | }
30 | );
31 |
32 | this.puzzleManager = new StegoPuzzleManager();
33 | this.symbl = new SymblConnector();
34 | this.templates = new TemplateEngine();
35 |
36 | this.setupTools();
37 | }
38 |
39 | async setupTools() {
40 | // Register tools handler
41 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
42 | switch (request.params.name) {
43 | case 'create_puzzle':
44 | return await this.createPuzzle(request.params.arguments);
45 | case 'decode_puzzle':
46 | return await this.decodePuzzle(request.params.arguments);
47 | case 'list_templates':
48 | return await this.getTemplates();
49 | case 'encode_message':
50 | return await this.encodeMessage(request.params.arguments);
51 | case 'decode_message':
52 | return await this.decodeMessage(request.params.arguments);
53 | case 'search_characters':
54 | return await this.searchCharacters(request.params.arguments);
55 | case 'get_zero_width_chars':
56 | return await this.getZeroWidthChars();
57 | default:
58 | throw new McpError(
59 | ErrorCode.MethodNotFound,
60 | `Unknown tool: ${request.params.name}`
61 | );
62 | }
63 | });
64 |
65 | // Register list tools handler
66 | this.server.setRequestHandler(ListToolsRequestSchema, async () => {
67 | return {
68 | tools: [
69 | {
70 | name: 'create_puzzle',
71 | description: 'Create a Unicode steganography puzzle',
72 | inputSchema: {
73 | type: 'object',
74 | properties: {
75 | template: { type: 'string', enum: ['quantum', 'orbital', 'glitch', 'void'] },
76 | message: { type: 'string' },
77 | secret: { type: 'string' },
78 | difficulty: { type: 'string', enum: ['easy', 'medium', 'hard'], default: 'medium' }
79 | },
80 | required: ['template', 'message', 'secret']
81 | }
82 | },
83 | {
84 | name: 'decode_puzzle',
85 | description: 'Decode a Unicode steganography puzzle',
86 | inputSchema: {
87 | type: 'object',
88 | properties: {
89 | encodedText: { type: 'string' }
90 | },
91 | required: ['encodedText']
92 | }
93 | },
94 | {
95 | name: 'list_templates',
96 | description: 'List available puzzle templates'
97 | },
98 | {
99 | name: 'encode_message',
100 | description: 'Encode a message using steganography',
101 | inputSchema: {
102 | type: 'object',
103 | properties: {
104 | message: { type: 'string' },
105 | secret: { type: 'string' },
106 | method: { type: 'string', enum: ['binary', 'trinary', 'random'], default: 'binary' }
107 | },
108 | required: ['message', 'secret']
109 | }
110 | },
111 | {
112 | name: 'decode_message',
113 | description: 'Decode a steganographic message',
114 | inputSchema: {
115 | type: 'object',
116 | properties: {
117 | encodedText: { type: 'string' },
118 | method: { type: 'string', enum: ['binary', 'trinary', 'random'], default: 'binary' }
119 | },
120 | required: ['encodedText']
121 | }
122 | },
123 | {
124 | name: 'search_characters',
125 | description: 'Search for Unicode characters',
126 | inputSchema: {
127 | type: 'object',
128 | properties: {
129 | query: { type: 'string' },
130 | category: { type: 'string', enum: ['zeroWidth', 'quantum', 'special'] }
131 | },
132 | required: ['query']
133 | }
134 | },
135 | {
136 | name: 'get_zero_width_chars',
137 | description: 'Get list of zero-width Unicode characters'
138 | }
139 | ]
140 | };
141 | });
142 | }
143 |
144 | // Tool implementations
145 | async createPuzzle(args) {
146 | try {
147 | const { template, message, secret, difficulty = 'medium' } = args;
148 |
149 | // Get template configuration
150 | const templateConfig = await this.templates.getTemplate(template, difficulty);
151 |
152 | // Create puzzle using selected template
153 | const puzzle = await this.puzzleManager.createPuzzle({
154 | template: templateConfig,
155 | message,
156 | secret,
157 | difficulty
158 | });
159 |
160 | return {
161 | content: [
162 | {
163 | type: 'text',
164 | text: JSON.stringify({
165 | status: 'success',
166 | puzzle,
167 | metadata: {
168 | template,
169 | difficulty,
170 | encodingType: templateConfig.encoding
171 | }
172 | }, null, 2)
173 | }
174 | ]
175 | };
176 | } catch (error) {
177 | throw new McpError(
178 | ErrorCode.InternalError,
179 | `Failed to create puzzle: ${error.message}`
180 | );
181 | }
182 | }
183 |
184 | async decodePuzzle(args) {
185 | try {
186 | const { encodedText } = args;
187 | const decoded = await this.puzzleManager.decodePuzzle(encodedText);
188 | return {
189 | content: [
190 | {
191 | type: 'text',
192 | text: JSON.stringify({ decoded }, null, 2)
193 | }
194 | ]
195 | };
196 | } catch (error) {
197 | throw new McpError(
198 | ErrorCode.InternalError,
199 | `Failed to decode puzzle: ${error.message}`
200 | );
201 | }
202 | }
203 |
204 | async getTemplates() {
205 | try {
206 | const templates = this.templates.listTemplates();
207 | return {
208 | content: [
209 | {
210 | type: 'text',
211 | text: JSON.stringify({ templates }, null, 2)
212 | }
213 | ]
214 | };
215 | } catch (error) {
216 | throw new McpError(
217 | ErrorCode.InternalError,
218 | `Failed to list templates: ${error.message}`
219 | );
220 | }
221 | }
222 |
223 | async encodeMessage(args) {
224 | try {
225 | const { message, secret, method = 'binary' } = args;
226 | const encoded = await this.puzzleManager.encodeSecret(message, secret, {
227 | pattern: method,
228 | difficulty: 'medium'
229 | });
230 | return {
231 | content: [
232 | {
233 | type: 'text',
234 | text: JSON.stringify({ encoded }, null, 2)
235 | }
236 | ]
237 | };
238 | } catch (error) {
239 | throw new McpError(
240 | ErrorCode.InternalError,
241 | `Failed to encode message: ${error.message}`
242 | );
243 | }
244 | }
245 |
246 | async decodeMessage(args) {
247 | try {
248 | const { encodedText, method = 'binary' } = args;
249 | const decoded = this.puzzleManager.decodeSecret(encodedText, method);
250 | return {
251 | content: [
252 | {
253 | type: 'text',
254 | text: JSON.stringify({ decoded }, null, 2)
255 | }
256 | ]
257 | };
258 | } catch (error) {
259 | throw new McpError(
260 | ErrorCode.InternalError,
261 | `Failed to decode message: ${error.message}`
262 | );
263 | }
264 | }
265 |
266 | async searchCharacters(args) {
267 | try {
268 | const { query, category } = args;
269 | const results = await this.symbl.searchCharacters(query, category);
270 | return {
271 | content: [
272 | {
273 | type: 'text',
274 | text: JSON.stringify({ results }, null, 2)
275 | }
276 | ]
277 | };
278 | } catch (error) {
279 | throw new McpError(
280 | ErrorCode.InternalError,
281 | `Failed to search characters: ${error.message}`
282 | );
283 | }
284 | }
285 |
286 | async getZeroWidthChars() {
287 | try {
288 | const chars = await this.symbl.getZeroWidthCharacters();
289 | return {
290 | content: [
291 | {
292 | type: 'text',
293 | text: JSON.stringify({ chars }, null, 2)
294 | }
295 | ]
296 | };
297 | } catch (error) {
298 | throw new McpError(
299 | ErrorCode.InternalError,
300 | `Failed to get zero-width characters: ${error.message}`
301 | );
302 | }
303 | }
304 |
305 | async start() {
306 | const transport = new StdioServerTransport();
307 | await this.server.connect(transport);
308 | console.error('Unicode Puzzles MCP server started');
309 | }
310 | }
311 |
312 | // Initialize and start server
313 | const server = new UnicodePuzzlesMCP();
314 | server.start().catch(console.error);
```