2026-06-06 20:12:40 +07:00
parent de84b2bf48
commit 97ac0f71f5
13682 changed files with 1125938 additions and 0 deletions
@@ -0,0 +1,17 @@
# Unity MCP Server
## Documentation
- MCP Documentation: https://modelcontextprotocol.io/docs
- MCP Specification: https://modelcontextprotocol.io/specification/2025-06-18
- MCP SDK: https://www.npmjs.com/package/@modelcontextprotocol/sdk
## API Keys
- Claude API: https://console.anthropic.com/
- Gemini API: https://aistudio.google.com/app/apikey
- OpenAI API: https://platform.openai.com/api-keys
## Setup
1. Run `npm install` to install dependencies
2. Configure API keys in .env file
3. Run `npm start` to start the server
# nexus
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 4541bcf45eb234d2084d1684d4e83b4d
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/README.md
uploadId: 920982
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5c621d6320333478b8306b899b61ab86
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,7 @@
{
"provider": "anthropic",
"model": "claude-3-opus-20240229",
"temperature": 0.7,
"max_tokens": 4096,
"tools": ["unity_create", "unity_modify", "unity_delete"]
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 7b83a451a66764293a632db12c102d92
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/ai-config/claude.json
uploadId: 920982
@@ -0,0 +1,6 @@
{
"provider": "github",
"model": "gpt-4",
"temperature": 0.5,
"tools": ["unity_create", "unity_modify", "code_generation"]
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 02c26a063763b46768be7b91db398d95
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/ai-config/copilot.json
uploadId: 920982
@@ -0,0 +1,6 @@
{
"provider": "google",
"model": "gemini-pro",
"temperature": 0.8,
"tools": ["unity_create", "unity_modify"]
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: e7817e5dc9aeb4b21be729f00bc21755
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/ai-config/gemini.json
uploadId: 920982
@@ -0,0 +1,131 @@
// Unity内チャットとデスクトップアプリのブリッジ処理
class BridgeHandler {
constructor() {
this.unitySocket = null;
this.desktopSocket = null;
this.conversationHistory = [];
this.pendingToolExecutions = new Map();
}
// Unity接続を設定
setUnityConnection(socket) {
this.unitySocket = socket;
// console.log('[Bridge] Unity connected');
// 既存の会話履歴を送信
if (this.conversationHistory.length > 0) {
socket.send(JSON.stringify({
type: 'conversation_history',
history: this.conversationHistory
}));
}
}
// デスクトップアプリ接続を設定
setDesktopConnection(socket) {
this.desktopSocket = socket;
// console.log('[Bridge] Desktop app connected');
}
// Unity → デスクトップアプリへのメッセージ転送
forwardToDesktop(message) {
if (!this.desktopSocket) {
// デスクトップアプリ未接続の場合はエラー
if (this.unitySocket) {
this.unitySocket.send(JSON.stringify({
type: 'error',
message: 'デスクトップアプリが接続されていません。Claude DesktopまたはGPT Desktopを起動してください。'
}));
}
return;
}
// 会話履歴に追加
this.conversationHistory.push({
role: 'user',
content: message.content,
timestamp: new Date()
});
// デスクトップアプリに転送
this.desktopSocket.send(JSON.stringify({
type: 'user_message',
content: message.content,
context: {
source: 'unity',
projectName: message.projectName || 'Unity Project',
history: this.conversationHistory.slice(-10) // 直近10件の履歴を含める
}
}));
}
// デスクトップアプリ → Unityへのメッセージ転送
forwardToUnity(message) {
if (!this.unitySocket) {
console.error('[Bridge] Unity not connected');
return;
}
// AIの応答を会話履歴に追加
if (message.type === 'assistant_response') {
this.conversationHistory.push({
role: 'assistant',
content: message.content,
timestamp: new Date()
});
}
// Unityに転送
this.unitySocket.send(JSON.stringify(message));
}
// ツール実行結果の処理
handleToolResult(toolId, result) {
if (this.pendingToolExecutions.has(toolId)) {
const { desktopRequestId } = this.pendingToolExecutions.get(toolId);
// デスクトップアプリに結果を送信
if (this.desktopSocket) {
this.desktopSocket.send(JSON.stringify({
type: 'tool_result',
requestId: desktopRequestId,
result: result
}));
}
this.pendingToolExecutions.delete(toolId);
}
}
// 接続切断の処理
handleDisconnect(type) {
if (type === 'unity') {
this.unitySocket = null;
// console.log('[Bridge] Unity disconnected');
} else if (type === 'desktop') {
this.desktopSocket = null;
// console.log('[Bridge] Desktop app disconnected');
// Unityに通知
if (this.unitySocket) {
this.unitySocket.send(JSON.stringify({
type: 'desktop_disconnected',
message: 'デスクトップアプリとの接続が切断されました'
}));
}
}
}
// 状態を取得
getStatus() {
return {
unityConnected: this.unitySocket !== null,
desktopConnected: this.desktopSocket !== null,
historyLength: this.conversationHistory.length,
pendingTools: this.pendingToolExecutions.size
};
}
}
module.exports = BridgeHandler;
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 41ee4dc4328d94f88b3d472abf8dce2e
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/bridge-handler.js
uploadId: 920982
@@ -0,0 +1,79 @@
unity_create_gameobject
unity_update_gameobject
unity_delete_gameobject
unity_duplicate_gameobject
unity_find_gameobjects_by_component
unity_group_gameobjects
unity_get_gameobjects_list
unity_get_gameobject_detail
unity_set_active_scene
unity_find_missing_references
unity_set_transform
unity_add_component
unity_update_component
unity_setup_camera
unity_create_virtual_camera
unity_create_freelook_camera
unity_setup_cinemachine_brain
unity_update_virtual_camera
unity_create_dolly_track
unity_add_collider_extension
unity_set_camera_priority
unity_set_camera_enabled
unity_update_camera_target
unity_update_brain_blend_settings
unity_get_active_camera_info
unity_capture_game_view
unity_capture_scene_view
unity_capture_region
unity_get_screenshot_result
unity_get_scene_info
unity_get_scene_summary
unity_load_scene
unity_unload_scene
unity_list_all_scenes
unity_add_scene_to_build
unity_get_scene_changes_since
unity_pause_scene
unity_search
unity_force_refresh_assets
unity_invoke_context_menu
unity_get_inspector_info
unity_get_selected_object_info
unity_get_component_details
unity_console
unity_analyze_console_logs
unity_create_ui
unity_setup_ui_canvas
unity_set_ui_anchor
unity_setup_ui_anchors
unity_create_ui_grid
unity_create_ui_dialog
unity_setup_safe_area
unity_create_material
unity_setup_material
unity_setup_lighting
unity_setup_reflection_probe
unity_create_light_probe_group
unity_setup_lighting_preset
unity_create_animator_controller
unity_add_animation_state
unity_create_animation_clip
unity_add_animation_transition
unity_bake_animation
unity_setup_physics
unity_setup_navmesh
unity_create_particle_system
unity_create_screen_fade
unity_create_screen_shake
unity_create_particle_preset
unity_create_prefab
unity_search_prefabs_by_component
unity_find_material_usage
unity_get_asset_dependencies
unity_check_folder
unity_create_folder
unity_list_assets
unity_create_audio_source
unity_setup_3d_audio
unity_list_packages
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 3153b89fb08084e9f95098bc38ab2fec
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/essential-tools-list.txt
uploadId: 920982
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
import re
# Read essential tools list
with open('essential-tools-list.txt', 'r') as f:
essential_tools = set(line.strip() for line in f if line.strip())
print(f"Essential tools count: {len(essential_tools)}")
# Read index.js
with open('index.js', 'r') as f:
content = f.read()
# Split by tool registrations
# Pattern: mcpServer.registerTool('tool_name', ...);
pattern = r"( mcpServer\.registerTool\('([^']+)',[\s\S]*?\n \}\);)"
matches = list(re.finditer(pattern, content))
print(f"Found {len(matches)} tool registrations")
# Find the start of tool registrations
first_match_start = matches[0].start() if matches else -1
last_match_end = matches[-1].end() if matches else -1
# Extract prefix (before first tool), suffix (after last tool), and tools
prefix = content[:first_match_start]
suffix = content[last_match_end:]
# Filter tools
filtered_tools = []
removed_count = 0
kept_count = 0
for match in matches:
tool_block = match.group(1)
tool_name = match.group(2)
if tool_name in essential_tools:
filtered_tools.append(tool_block)
kept_count += 1
print(f"✓ Keeping: {tool_name}")
else:
removed_count += 1
print(f"✗ Removing: {tool_name}")
# Reconstruct file
output = prefix + '\n\n'.join(filtered_tools) + suffix
# Write to index-essential.js
with open('index-essential.js', 'w') as f:
f.write(output)
print(f"\n=== Summary ===")
print(f"Kept: {kept_count} tools")
print(f"Removed: {removed_count} tools")
print(f"Output written to: index-essential.js")
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: abb00cd6e72444e239439930c4157fe0
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/filter-essential-tools.py
uploadId: 920982
@@ -0,0 +1,613 @@
#!/usr/bin/env node
/**
* Synaptic AI Pro - HTTP Server (Standalone)
*
* Node.js based HTTP server for Unity control.
* Runs outside Unity process - no domain reload issues.
*
* Usage:
* node http-server.js [port]
* node http-server.js 8086
*
* Environment variables:
* HTTP_PORT - Server port (default: 8086)
*
* HTTP and WebSocket run on the SAME port.
* - HTTP: http://localhost:8086/
* - WebSocket: ws://localhost:8086/
*/
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const cors = require('cors');
const fs = require('fs');
const path = require('path');
// ===== Configuration =====
const PORT = parseInt(process.argv[2]) || parseInt(process.env.HTTP_PORT) || 8086;
// ===== Parent-PID watchdog =====
// When launched detached from Unity's JobObject (Windows), we lose the
// guarantee that the OS will reap us if Unity dies. Poll the parent PID
// every 5s and self-terminate if it's gone — prevents orphaned node.exe.
(function setupParentWatchdog() {
const arg = (process.argv.find(a => typeof a === 'string' && a.startsWith('--parent-pid=')) || '');
const parentPid = arg ? parseInt(arg.split('=')[1], 10) : 0;
if (!parentPid) return;
setInterval(() => {
try {
// Signal 0 is a "check process exists" probe on both POSIX and Windows
process.kill(parentPid, 0);
} catch (_e) {
console.error(`[watchdog] Parent PID ${parentPid} gone. Exiting.`);
process.exit(0);
}
}, 5000).unref();
})();
// ===== File log (when --log=path is provided) =====
// Detached mode disables stdout/stderr piping from C# side, so route logs
// to a file instead. Best-effort: silent failure if file can't be opened.
(function setupFileLog() {
const arg = (process.argv.find(a => typeof a === 'string' && a.startsWith('--log=')) || '');
const logPath = arg ? arg.split('=').slice(1).join('=') : '';
if (!logPath) return;
try {
const fs = require('fs');
const stream = fs.createWriteStream(logPath, { flags: 'a' });
const wrap = (orig) => (...args) => {
try {
const line = args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ');
stream.write(`[${new Date().toISOString()}] ${line}\n`);
} catch (_) { /* ignore */ }
try { orig.apply(console, args); } catch (_) { /* detached: stdout may be closed */ }
};
console.log = wrap(console.log);
console.error = wrap(console.error);
console.warn = wrap(console.warn);
} catch (_e) {
/* ignore: log is best-effort */
}
})();
const app = express();
app.use(cors());
app.use(express.json({ limit: '10mb' }));
const server = http.createServer(app);
// ===== Tool Registry =====
let toolRegistry = {};
let toolCategories = {};
function loadToolRegistry() {
try {
const registryPath = path.join(__dirname, 'tool-registry.json');
if (fs.existsSync(registryPath)) {
toolRegistry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
// Build category index
toolCategories = {};
for (const [name, tool] of Object.entries(toolRegistry)) {
const cat = tool.category || 'Other';
if (!toolCategories[cat]) toolCategories[cat] = [];
toolCategories[cat].push({ name, ...tool });
}
console.error(`[HTTP] Loaded ${Object.keys(toolRegistry).length} tools in ${Object.keys(toolCategories).length} categories`);
} else {
console.error('[HTTP] Warning: tool-registry.json not found');
}
} catch (e) {
console.error(`[HTTP] Failed to load tool registry: ${e.message}`);
}
}
loadToolRegistry();
// ===== Unity WebSocket Connection =====
let unitySocket = null;
let wss = null;
const pendingRequests = new Map();
let requestCounter = 0;
function setupWebSocket() {
// Attach WebSocket to the same HTTP server (same port)
wss = new WebSocket.Server({ server });
wss.on('connection', (ws, req) => {
const isUnity = req.headers['x-client-type'] === 'unity' || req.url === '/unity';
if (isUnity || !req.url || req.url === '/') {
if (unitySocket) {
try { unitySocket.close(); } catch (e) {}
}
unitySocket = ws;
console.error(`[HTTP] Unity connected via WebSocket`);
ws.on('message', (message) => {
try {
const data = JSON.parse(message);
// Handle response from Unity
if (data.operationId && pendingRequests.has(data.operationId)) {
const { resolve } = pendingRequests.get(data.operationId);
pendingRequests.delete(data.operationId);
resolve(data);
}
} catch (e) {
console.error(`[HTTP] WebSocket message error: ${e.message}`);
}
});
ws.on('close', () => {
console.error('[HTTP] Unity disconnected');
unitySocket = null;
});
ws.on('error', (err) => {
console.error(`[HTTP] WebSocket error: ${err.message}`);
});
// ESC-0108 fix (reported by xvpower., 2026-05-22):
// Mono's `ClientWebSocket` (Unity) does NOT auto-respond to
// WebSocket protocol-level `ping` frames with `pong` — unlike
// .NET 5+ runtime. The previous `ws.ping() / ws.on('pong')`
// heartbeat therefore terminated the connection after ~30s on
// every Unity 6.3 LTS environment.
// Switch to last-message-seen tracking: Unity already emits its
// own application-level traffic (operation_response, debug logs)
// so we just observe the timestamp of any received frame.
ws.lastSeen = Date.now();
const recordSeen = () => { ws.lastSeen = Date.now(); };
ws.on('message', recordSeen);
ws.on('ping', recordSeen);
ws.on('pong', recordSeen);
}
});
// Heartbeat: declare a connection dead only if no inbound frame for N ms.
// Override via env (UNITY_STALE_TIMEOUT_MS); default 60 000 keeps a safe
// margin over Unity's slowest legitimate quiet period.
const STALE_TIMEOUT_MS = parseInt(process.env.UNITY_STALE_TIMEOUT_MS || '60000', 10);
const heartbeatInterval = setInterval(() => {
if (!wss.clients) return;
wss.clients.forEach((ws) => {
if (!ws.lastSeen) return;
if (Date.now() - ws.lastSeen > STALE_TIMEOUT_MS) {
console.error(`[HTTP] Unity stale (no frames for ${STALE_TIMEOUT_MS}ms) - closing connection`);
try { ws.terminate(); } catch {}
}
});
}, 30000);
wss.on('close', () => {
clearInterval(heartbeatInterval);
});
wss.on('error', (err) => {
console.error(`[HTTP] WebSocket server error: ${err.message}`);
});
console.error(`[HTTP] WebSocket server attached to HTTP server (port ${PORT})`);
}
// Execute tool via Unity WebSocket
function executeOnUnity(toolName, params, timeout = 30000) {
return new Promise((resolve, reject) => {
if (!unitySocket || unitySocket.readyState !== WebSocket.OPEN) {
reject(new Error('Unity not connected. Open Unity with Synaptic AI Pro project.'));
return;
}
const operationId = `http_${++requestCounter}_${Date.now()}`;
const operation = convertToolToOperation(toolName);
const message = {
type: 'operation',
operationType: operation,
operationId: operationId,
parameters: params || {}
};
const timer = setTimeout(() => {
pendingRequests.delete(operationId);
reject(new Error(`Timeout waiting for Unity response (${timeout}ms)`));
}, timeout);
pendingRequests.set(operationId, {
resolve: (data) => {
clearTimeout(timer);
resolve(data);
},
reject: (err) => {
clearTimeout(timer);
reject(err);
}
});
unitySocket.send(JSON.stringify(message));
});
}
// Convert tool name to Unity operation type
function convertToolToOperation(toolName) {
// Remove unity_ prefix and convert to UPPER_SNAKE_CASE
let name = toolName;
if (name.startsWith('unity_')) {
name = name.substring(6);
}
return name.toUpperCase();
}
// ===== Prompt Generation =====
function getAIControlPrompt() {
return `# Synaptic AI Pro (Unity) HTTP Control Instructions
## Prerequisites
- Unity must be open with Synaptic AI Pro project loaded
- HTTP Server running: node http-server.js ${PORT}
- Unity connects via WebSocket automatically
## Endpoints
- GET / or /prompt - Get this AI control prompt + full tools reference
- GET /health - Server status and Unity connection
- GET /categories - List all tool categories
- GET /tools - Full tool registry
- GET /tools/category/:cat - List tools in category with inputSchema
- GET /tools/search?q=keyword - Search tools by name, description, parameters
- GET /tools/reference - Get ALL tools in Markdown format (TOKEN SAVER)
- GET /resources - List available resources (MCP-style)
- GET /resources/read?uri=synaptic://tools/reference - Read a resource
- POST /execute - Execute single tool (RECOMMENDED)
- POST /batch - Execute multiple tools at once (RECOMMENDED)
## Verify connection
curl http://localhost:${PORT}/health
## Tool discovery
curl http://localhost:${PORT}/categories
curl http://localhost:${PORT}/tools/category/scene
## Tool search
curl "http://localhost:${PORT}/tools/search?q=material"
curl "http://localhost:${PORT}/tools/search?q=color&category=Material&limit=10"
## Get full tools reference (RECOMMENDED at session start)
curl "http://localhost:${PORT}/tools/reference"
curl "http://localhost:${PORT}/tools/reference?format=compact"
## Single tool execution (RECOMMENDED)
curl -X POST http://localhost:${PORT}/execute -H "Content-Type: application/json" -d '{"tool":"unity_create_gameobject","params":{"name":"MyCube","type":"cube"}}'
## Batch execution (RECOMMENDED for multiple operations)
curl -X POST http://localhost:${PORT}/batch -H "Content-Type: application/json" -d '[
{"tool":"unity_create_gameobject","params":{"name":"Cube1","type":"cube"}},
{"tool":"unity_set_transform","params":{"name":"Cube1","position":"2,0,0"}},
{"tool":"unity_create_gameobject","params":{"name":"Sphere1","type":"sphere"}}
]'
## If connection fails
- "Unity not connected" → Open Unity project with Synaptic AI Pro
- Connection refused → HTTP server not running (node http-server.js)
## Notes
- All responses are JSON
- Use /batch for multiple operations (more efficient)
- 30 second timeout per request
`;
}
// ===== Tools Reference Generation =====
function getToolsReference(format = 'markdown', category = null) {
const tools = category
? (toolCategories[category] || [])
: Object.entries(toolRegistry).map(([name, t]) => ({ name, ...t }));
if (format === 'compact') {
// Compact format: name | description (one line per tool)
let output = '# Synaptic AI Pro - Tools Reference (Compact)\n\n';
output += `Total: ${tools.length} tools\n\n`;
const byCategory = {};
for (const tool of tools) {
const cat = tool.category || 'Other';
if (!byCategory[cat]) byCategory[cat] = [];
byCategory[cat].push(tool);
}
for (const [cat, catTools] of Object.entries(byCategory).sort()) {
output += `## ${cat} (${catTools.length})\n`;
for (const tool of catTools) {
const desc = (tool.description || '').split('.')[0].substring(0, 80);
output += `- ${tool.name}: ${desc}\n`;
}
output += '\n';
}
return output;
}
// Full markdown format with inputSchema
let output = '# Synaptic AI Pro - Tools Reference\n\n';
output += `Total: ${tools.length} tools\n\n`;
const byCategory = {};
for (const tool of tools) {
const cat = tool.category || 'Other';
if (!byCategory[cat]) byCategory[cat] = [];
byCategory[cat].push(tool);
}
for (const [cat, catTools] of Object.entries(byCategory).sort()) {
output += `## ${cat}\n\n`;
for (const tool of catTools) {
output += `### ${tool.name}\n`;
output += `${tool.description || 'No description'}\n\n`;
if (tool.inputSchema && tool.inputSchema.properties) {
output += '**Parameters:**\n';
for (const [param, schema] of Object.entries(tool.inputSchema.properties)) {
const required = (tool.inputSchema.required || []).includes(param) ? ' (required)' : '';
const desc = schema.description || '';
output += `- \`${param}\`${required}: ${desc}\n`;
}
output += '\n';
}
}
}
return output;
}
// ===== HTTP Routes =====
// Root - return prompt + full tools reference
app.get('/', (req, res) => {
const prompt = getAIControlPrompt();
const toolsRef = getToolsReference('markdown');
res.type('text/plain').send(prompt + '\n\n---\n\n' + toolsRef);
});
// Prompt endpoint
app.get('/prompt', (req, res) => {
const prompt = getAIControlPrompt();
const toolsRef = getToolsReference('markdown');
res.type('text/plain').send(prompt + '\n\n---\n\n' + toolsRef);
});
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'ok',
server: 'Synaptic AI Pro Unity HTTP Server',
port: PORT,
tools: Object.keys(toolRegistry).length,
unityConnected: unitySocket !== null && unitySocket.readyState === WebSocket.OPEN,
toolCount: Object.keys(toolRegistry).length,
categoryCount: Object.keys(toolCategories).length
});
});
// Categories list
app.get('/categories', (req, res) => {
const categories = Object.entries(toolCategories)
.map(([name, tools]) => ({ name, count: tools.length }))
.sort((a, b) => a.name.localeCompare(b.name));
res.json({ categories });
});
// Full tool registry
app.get('/tools', (req, res) => {
res.json(toolRegistry);
});
// Tool list (Synaptic Code compatible)
app.get('/tools/list', (req, res) => {
const toolNames = Object.keys(toolRegistry);
res.json({
tools: toolNames,
count: toolNames.length
});
});
// Tools by category
app.get('/tools/category/:category', (req, res) => {
const category = req.params.category;
const tools = toolCategories[category];
if (!tools) {
return res.status(404).json({
error: `Category not found: ${category}`,
available: Object.keys(toolCategories).sort()
});
}
res.json({ category, count: tools.length, tools });
});
// Tool search
app.get('/tools/search', (req, res) => {
const query = (req.query.q || '').toLowerCase();
const categoryFilter = req.query.category;
const limit = parseInt(req.query.limit) || 20;
if (!query) {
return res.status(400).json({ error: 'Missing query parameter: q' });
}
const results = [];
const keywords = query.split(/\s+/);
for (const [name, tool] of Object.entries(toolRegistry)) {
if (categoryFilter && tool.category !== categoryFilter) continue;
let score = 0;
const searchText = `${name} ${tool.title || ''} ${tool.description || ''}`.toLowerCase();
for (const kw of keywords) {
if (name.toLowerCase().includes(kw)) score += 10;
if ((tool.title || '').toLowerCase().includes(kw)) score += 5;
if ((tool.description || '').toLowerCase().includes(kw)) score += 2;
}
if (score > 0) {
results.push({ name, score, ...tool });
}
}
results.sort((a, b) => b.score - a.score);
const limited = results.slice(0, limit);
res.json({ query, count: limited.length, total: results.length, results: limited });
});
// Tools reference (markdown)
app.get('/tools/reference', (req, res) => {
const format = req.query.format || 'markdown';
const category = req.query.category || null;
const reference = getToolsReference(format, category);
res.type('text/plain').send(reference);
});
// Resources list (MCP-style)
app.get('/resources', (req, res) => {
res.json({
resources: [
{
uri: 'synaptic://tools/reference',
name: 'Tools Reference (Compact)',
description: 'Complete list of all Unity tools in compact format (~30KB)',
mimeType: 'text/markdown'
},
{
uri: 'synaptic://tools/reference/full',
name: 'Tools Reference (Full)',
description: 'Complete list of all Unity tools with full parameter details (~100KB)',
mimeType: 'text/markdown'
}
]
});
});
// Read resource
app.get('/resources/read', (req, res) => {
const uri = req.query.uri;
if (!uri) {
return res.status(400).json({ error: 'Missing uri parameter' });
}
let content = null;
let mimeType = 'text/markdown';
if (uri === 'synaptic://tools/reference') {
content = getToolsReference('compact');
} else if (uri === 'synaptic://tools/reference/full') {
content = getToolsReference('markdown');
} else {
return res.status(404).json({ error: `Unknown resource: ${uri}` });
}
res.json({
contents: [
{ uri, mimeType, text: content }
]
});
});
// Execute single tool
app.post('/execute', async (req, res) => {
try {
const { tool, params, timeout } = req.body;
if (!tool) {
return res.status(400).json({ error: 'Missing tool name' });
}
const result = await executeOnUnity(tool, params || {}, timeout || 30000);
res.json(result);
} catch (e) {
res.status(500).json({ error: e.message });
}
});
// Batch execute
app.post('/batch', async (req, res) => {
try {
const operations = req.body;
if (!Array.isArray(operations)) {
return res.status(400).json({ error: 'Body must be an array of operations' });
}
const results = [];
for (const op of operations) {
try {
const result = await executeOnUnity(op.tool, op.params || {});
results.push({ tool: op.tool, success: true, result });
} catch (e) {
results.push({ tool: op.tool, success: false, error: e.message });
}
}
res.json({ count: results.length, results });
} catch (e) {
res.status(500).json({ error: e.message });
}
});
// Legacy: POST /tool/:name
app.post('/tool/:name', async (req, res) => {
try {
const tool = req.params.name;
const params = req.body || {};
const result = await executeOnUnity(tool, params);
res.json(result);
} catch (e) {
res.status(500).json({ error: e.message });
}
});
// ===== Startup =====
server.listen(PORT, () => {
// Setup WebSocket after server starts (attached to same port)
setupWebSocket();
console.error(`
╔═══════════════════════════════════════════════════════════╗
║ Synaptic AI Pro - HTTP Server ║
╠═══════════════════════════════════════════════════════════╣
║ Port: ${PORT.toString().padEnd(5)}
║ HTTP: http://localhost:${PORT.toString().padEnd(5)}
║ WebSocket: ws://localhost:${PORT.toString().padEnd(5)}
╠═══════════════════════════════════════════════════════════╣
║ Endpoints: ║
║ GET / - AI prompt + tools reference ║
║ GET /health - Connection status ║
║ GET /tools - Full tool registry ║
║ GET /categories - Tool categories ║
║ POST /execute - Execute tool ║
║ POST /batch - Batch execute ║
╠═══════════════════════════════════════════════════════════╣
║ Waiting for Unity to connect... ║
╚═══════════════════════════════════════════════════════════╝
`);
});
// Graceful shutdown
process.on('SIGINT', () => {
console.error('\n[HTTP] Shutting down...');
if (wss) wss.close();
server.close();
process.exit(0);
});
process.on('SIGTERM', () => {
console.error('\n[HTTP] Shutting down...');
if (wss) wss.close();
server.close();
process.exit(0);
});
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 1d8af1b2b57c946c29ca26b83004613a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/http-server.js
uploadId: 920982
@@ -0,0 +1,611 @@
#!/usr/bin/env node
/**
* hub-server.js - MCP Hub Server for Unity Synaptic v1.1.2 (Dynamic Mode)
*
* Provides dynamic tool loading for GitHub Copilot (VS Code):
* - Exposes only ~10 management tools initially
* - Loads additional tools on-demand via select_tools()
* - Uses MCP Notification System to update tool list
* - Requires client support for notifications/tools/list_changed
*
* Supported Clients:
* - ✅ GitHub Copilot (VS Code)
* - ❌ Claude Desktop (use index.js instead)
* - ❌ Cursor (use index.js instead)
*
* Usage:
* node hub-server.js (stdio mode for MCP clients)
* OPENAI_API_KEY=sk-xxx node hub-server.js (optional, for keyword search)
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import WebSocket, { WebSocketServer } from 'ws';
import { z } from 'zod';
import express from 'express';
import http from 'http';
import cors from 'cors';
import {
filterToolsByCategories,
searchToolsByKeywords,
getAvailableCategories,
loadToolRegistry
} from './utils/tool-loader.js';
// HTTP and WebSocket server setup
const app = express();
app.use(cors());
app.use(express.json());
const httpServer = http.createServer(app);
let wss = null; // WebSocket server for Unity connections
// Unity WebSocket connection
let unityWs = null;
// Unity command tracking (same as index.js)
const pendingRequests = new Map();
let requestId = 0;
// Load tool registry (embeddings + metadata)
const toolRegistry = loadToolRegistry();
if (!toolRegistry) {
console.error('[Hub] WARNING: tool-registry.json not found. Run: node scripts/generate-tool-registry.js');
console.error('[Hub] Hub server will work with limited functionality');
}
// Initialize MCP Server with dynamic tool capabilities
const server = new Server(
{
name: 'unity-synaptic-hub',
version: '1.1.0',
},
{
capabilities: {
tools: {
listChanged: true // Support for dynamic tool updates via notifications
},
},
}
);
// Essential tools (always available)
//
// RECOMMENDED WORKFLOW FOR LLMs:
// 1. Use list_available_categories() to see all 31 categories
// 2. Use search_tools(query: "keyword") to find specific tools
// 3. Use select_tools(categories: ["Cat1", "Cat2"]) to load needed tools
// 4. Call the loaded Unity tools (e.g., unity_create_gameobject)
//
// Example: User wants to add visual effects
// → search_tools(query: "bloom effect")
// → select_tools(categories: ["VFX"])
// → unity_create_bloom(...)
//
const essentialTools = [
{
name: 'select_tools',
title: '🔧 Load Unity Tools by Category',
description: 'Load additional Unity tools dynamically. Call list_available_categories first to see available categories, or use search_tools to find specific tools. Without calling this, only 8 basic tools are available. Example: select_tools({categories: ["VFX", "Camera"]}) loads visual effects and camera tools.',
inputSchema: z.object({
categories: z.array(z.enum([
'GameObject', 'Transform', 'Material', 'Lighting', 'Camera', 'Physics',
'UI', 'Animation', 'Cinemachine', 'Scene', 'GOAP', 'Audio', 'Input',
'VFX', 'Shader', 'Weather', 'TimeOfDay', 'Editor', 'Package', 'Build',
'Monitoring', 'AssetManagement', 'Optimization', 'Batch', 'GameSystems',
'AI', 'Debug', 'Timeline', 'Scripting', 'Screenshot', 'Utility'
])).optional().describe('Tool categories to load (see list_available_categories for options)'),
keywords: z.array(z.string()).optional().describe('Optional: filter by keywords (e.g. ["camera", "color"])'),
maxTools: z.number().optional().default(50).describe('Max tools to load (default: 50, max: 100)')
})
},
{
name: 'list_available_categories',
title: '📋 List Available Tool Categories',
description: 'Show all 31 Unity tool categories with counts and descriptions. RECOMMENDED: Call this first to discover available categories, then use select_tools to load the tools you need.',
inputSchema: z.object({})
},
{
name: 'search_tools',
title: '🔍 Search Unity Tools by Keyword',
description: 'Find Unity tools by keyword search. Supports single or multiple keywords (e.g. "camera", "particle effect", "material color"). Returns tool names, descriptions, categories, and relevance scores. Use this when you know what you want but not which category it belongs to.',
inputSchema: z.object({
query: z.string().describe('Search keyword(s) - single word or phrase'),
limit: z.number().optional().default(20).describe('Max results (default: 20)')
})
},
{
name: 'unity_get_scene_summary',
title: 'Get Scene Summary',
description: 'Get lightweight Unity scene overview (<200KB). Returns: scene name, GameObject count, cameras, lights, root GameObjects list.',
inputSchema: z.object({})
},
{
name: 'unity_get_gameobjects_list',
title: 'Get GameObjects List',
description: 'Get filtered list of GameObjects. Supports filters: layer, tag, name, activeOnly. Max 100 results.',
inputSchema: z.object({
layerFilter: z.string().optional(),
tagFilter: z.string().optional(),
nameFilter: z.string().optional(),
activeOnly: z.boolean().optional(),
maxCount: z.number().optional().default(100)
})
},
{
name: 'unity_get_gameobject_detail',
title: 'Get GameObject Detail',
description: 'Get detailed info for specific GameObject by name or instanceId.',
inputSchema: z.object({
nameOrId: z.string()
})
},
{
name: 'unity_undo',
title: 'Undo Unity Operation',
description: 'Undo last Unity operation',
inputSchema: z.object({})
},
{
name: 'unity_redo',
title: 'Redo Unity Operation',
description: 'Redo Unity operation',
inputSchema: z.object({})
}
];
// Currently active tools (starts with essential only)
let activeTools = [...essentialTools];
// Category metadata used for validation/default selection
const ALL_TOOL_CATEGORIES = [
'GameObject', 'Transform', 'Material', 'Lighting', 'Camera', 'Physics',
'UI', 'Animation', 'Cinemachine', 'Scene', 'GOAP', 'Audio', 'Input',
'VFX', 'Shader', 'Weather', 'TimeOfDay', 'Editor', 'Package', 'Build',
'Monitoring', 'AssetManagement', 'Optimization', 'Batch', 'GameSystems',
'AI', 'Debug', 'Timeline', 'Scripting', 'Screenshot', 'Utility', 'Other'
];
// Default categories to load when no parameters are supplied (covers core workflows)
const DEFAULT_TOOL_CATEGORIES = [
'GameObject', 'Transform', 'Material', 'Lighting', 'Camera', 'Physics',
'UI', 'Animation', 'Scene', 'Audio', 'Utility'
];
const CATEGORY_SET = new Set(ALL_TOOL_CATEGORIES);
function normalizeInputList(value) {
if (!value) {
return [];
}
if (Array.isArray(value)) {
return value
.map(item => (typeof item === 'string' ? item : String(item)).trim())
.filter(Boolean);
}
if (typeof value === 'string') {
return value
.split(/[,|]/)
.map(item => item.trim())
.filter(Boolean);
}
return [];
}
function normalizeCategories(value) {
return normalizeInputList(value).filter(item => CATEGORY_SET.has(item));
}
function normalizeKeywords(value) {
return normalizeInputList(value);
}
// Setup WebSocket server to accept Unity connections
function setupWebSocketServer() {
if (!wss) return;
wss.on('connection', (ws, req) => {
const isUnity = req.headers['x-client-type'] === 'unity' || req.url === '/unity';
if (isUnity) {
// Unity client connected
if (unityWs) {
unityWs.close();
}
unityWs = ws;
console.error('[Hub] Unity connected via WebSocket');
// Handle Unity responses (same format as index.js)
ws.on('message', (data) => {
try {
const response = JSON.parse(data.toString());
// Unity sends: { id, type: 'operation_result', data: { success: true/false }, content: "result" }
if (response.type === 'operation_result' && response.id) {
// Convert id to number like index.js does
const numericId = typeof response.id === 'string' ? parseInt(response.id) : response.id;
if (pendingRequests.has(numericId)) {
const { resolve, reject, timeout } = pendingRequests.get(numericId);
clearTimeout(timeout);
pendingRequests.delete(numericId);
// Match index.js format: resolve with content, not whole response
if (response.data && response.data.success) {
resolve(response.content);
} else {
reject(new Error(response.content || 'Unity command failed'));
}
}
}
} catch (error) {
console.error('[Hub] Failed to parse Unity response:', error.message);
}
});
ws.on('close', () => {
console.error('[Hub] Unity disconnected');
unityWs = null;
});
ws.on('error', (err) => {
console.error('[Hub] Unity WebSocket error:', err.message);
});
} else {
console.error('[Hub] Unknown client connected, closing connection');
ws.close();
}
});
}
// Helper function for delay
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Single command attempt to Unity
async function forwardToUnityOnce(command, params, id) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
pendingRequests.delete(id);
reject(new Error('timeout'));
}, 15000);
pendingRequests.set(id, { resolve, reject, timeout });
const message = JSON.stringify({
id,
type: 'tool_call',
tool: command,
command,
parameters: params || {}
});
unityWs.send(message);
});
}
// Forward command to Unity with auto-retry (enhanced for compilation waiting)
async function forwardToUnity(toolName, params) {
const MAX_RETRIES = 30; // 30 retries for very long compilations
const RETRY_DELAY = 10000; // 10 seconds between retries
const COMPILE_WAIT_DELAY = 10000; // 10 seconds when compiling (total max: ~5 min)
const command = toolName.startsWith('unity_') ? toolName.substring(6) : toolName;
let lastError = null;
let isLikelyCompiling = false;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
if (!unityWs || unityWs.readyState !== WebSocket.OPEN) {
lastError = new Error('Unity not connected');
isLikelyCompiling = true;
if (attempt < MAX_RETRIES) {
const waitTime = isLikelyCompiling ? COMPILE_WAIT_DELAY : RETRY_DELAY;
console.error(`[Hub] Unity not connected (attempt ${attempt}/${MAX_RETRIES}). Waiting ${waitTime/1000}s...`);
await sleep(waitTime);
continue;
}
break;
}
const id = ++requestId;
try {
const result = await forwardToUnityOnce(command, params, id);
// Format response for MCP client
const formattedResult = {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}]
};
if (attempt > 1) {
formattedResult.content[0].text += `\n[Note: Succeeded on retry attempt ${attempt}]`;
}
return formattedResult;
} catch (error) {
lastError = error;
if (error.message === 'timeout') {
isLikelyCompiling = true;
}
if (attempt < MAX_RETRIES) {
const waitTime = isLikelyCompiling ? COMPILE_WAIT_DELAY : RETRY_DELAY;
console.error(`[Hub] Command failed (attempt ${attempt}/${MAX_RETRIES}): ${error.message}. Retrying in ${waitTime/1000}s...`);
await sleep(waitTime);
}
}
}
throw new Error(`Unity not connected (after ${MAX_RETRIES} attempts). Unity may be recompiling - wait and try again.`);
}
// Handle select_tools - load tools dynamically
async function handleSelectTools(params = {}) {
const {
categories = [],
keywords = [],
maxTools = 50
} = params;
let normalizedCategories = normalizeCategories(categories);
const normalizedKeywords = normalizeKeywords(keywords);
if (normalizedCategories.length === 0 && normalizedKeywords.length === 0) {
normalizedCategories = [...DEFAULT_TOOL_CATEGORIES];
console.error('[Hub] select_tools called without parameters using default categories:', normalizedCategories.join(', '));
}
if (!toolRegistry) {
return {
content: [{
type: 'text',
text: JSON.stringify({
error: 'Tool registry not available. Run: node scripts/generate-tool-registry.js'
}, null, 2)
}],
isError: true
};
}
let selectedToolNames = [];
// Filter by categories
if (normalizedCategories.length > 0) {
selectedToolNames = filterToolsByCategories(normalizedCategories, toolRegistry);
}
// Further filter by keywords using embedding search
if (normalizedKeywords.length > 0) {
const query = normalizedKeywords.join(' ');
const searchResults = await searchToolsByKeywords(query, 200, toolRegistry);
if (selectedToolNames.length > 0) {
// Intersect with category results
const searchNames = new Set(searchResults.map(r => r.name));
selectedToolNames = selectedToolNames.filter(name => searchNames.has(name));
} else {
// Use search results only
selectedToolNames = searchResults.map(r => r.name);
}
}
// Limit results
selectedToolNames = selectedToolNames.slice(0, Math.min(maxTools, 100));
// Convert tool names to full tool definitions
// Use passthrough schema to accept any parameters (forwarded to Unity)
const selectedTools = selectedToolNames.map(name => {
const meta = toolRegistry[name];
return {
name: name,
title: meta.title || name,
description: meta.description || '',
inputSchema: z.object({}).passthrough() // Accept any parameters, forward to Unity
};
});
// Update active tools (essential + selected)
activeTools = [...essentialTools, ...selectedTools];
console.error(`[Hub] Loaded ${selectedTools.length} tools. Total active: ${activeTools.length}`);
// Send notification to client that tool list has changed
// This triggers the client to call tools/list again
try {
await server.notification({
method: 'notifications/tools/list_changed'
});
console.error('[Hub] Sent tools/list_changed notification to client');
} catch (error) {
console.error('[Hub] Warning: Failed to send notification:', error.message);
// Continue anyway - client might not support notifications
}
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
loaded_tools: selectedTools.length,
total_active: activeTools.length,
categories: normalizedCategories,
message: 'Tools loaded. Client should refresh tool list automatically.',
tools: selectedTools.map(t => ({
name: t.name,
category: toolRegistry[t.name]?.category
}))
}, null, 2)
}]
};
}
// Handle list_available_categories
async function handleListCategories() {
const categories = getAvailableCategories();
return {
content: [{
type: 'text',
text: JSON.stringify({ categories }, null, 2)
}]
};
}
// Handle search_tools
async function handleSearchTools(params) {
const { query, limit = 20 } = params;
if (!toolRegistry) {
return {
content: [{
type: 'text',
text: JSON.stringify({
error: 'Tool registry not available'
}, null, 2)
}],
isError: true
};
}
const results = await searchToolsByKeywords(query, limit, toolRegistry);
return {
content: [{
type: 'text',
text: JSON.stringify({
query,
results: results.map(r => ({
name: r.name,
description: r.description,
category: r.category,
relevance: r.score.toFixed(3)
}))
}, null, 2)
}]
};
}
// MCP handlers
server.setRequestHandler(ListToolsRequestSchema, async () => {
console.error(`[Hub] ListTools called - returning ${activeTools.length} tools`);
console.error(`[Hub] Essential tools: ${essentialTools.map(t => t.name).join(', ')}`);
return {
tools: activeTools.map(tool => {
// Convert Zod schema to JSON Schema format
let inputSchema;
if (tool.inputSchema && tool.inputSchema._def) {
// Extract properties from Zod schema
const zodShape = tool.inputSchema._def.shape?.() || {};
const properties = {};
const required = [];
for (const [key, value] of Object.entries(zodShape)) {
if (value._def) {
properties[key] = {
type: value._def.typeName === 'ZodString' ? 'string' :
value._def.typeName === 'ZodNumber' ? 'number' :
value._def.typeName === 'ZodBoolean' ? 'boolean' :
value._def.typeName === 'ZodArray' ? 'array' :
'string',
description: value._def.description || undefined
};
if (!value._def.isOptional) {
required.push(key);
}
}
}
inputSchema = {
type: 'object',
properties,
...(required.length > 0 ? { required } : {})
};
} else {
// Fallback for simple schemas
inputSchema = {
type: 'object',
properties: {},
additionalProperties: true
};
}
return {
name: tool.name,
description: tool.description,
inputSchema
};
})
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
console.error(`[Hub] Tool call: ${name}, args:`, JSON.stringify(args));
console.error(`[Hub] Available tools: ${activeTools.map(t => t.name).join(', ')}`);
try {
// Hub management tools
if (name === 'select_tools') {
return await handleSelectTools(args);
}
if (name === 'list_available_categories') {
return await handleListCategories();
}
if (name === 'search_tools') {
return await handleSearchTools(args);
}
// All other tools forward to Unity
return await forwardToUnity(name, args);
} catch (error) {
return {
content: [{
type: 'text',
text: JSON.stringify({
error: error.message
}, null, 2)
}],
isError: true
};
}
});
// Start server
async function main() {
// Start MCP server with Stdio transport (for VS Code)
const transport = new StdioServerTransport();
await server.connect(transport);
// Create WebSocket server for Unity connections
wss = new WebSocketServer({ server: httpServer });
setupWebSocketServer();
// Start HTTP server
const port = process.env.PORT || 8090;
httpServer.listen(port, () => {
console.error('[Hub] Unity Synaptic Hub Server v1.1.0 started');
console.error(`[Hub] WebSocket server listening on port ${port}`);
console.error(`[Hub] Essential tools loaded: ${essentialTools.length}`);
console.error('[Hub] Waiting for Unity connection and select_tools() calls...');
});
}
main().catch(error => {
console.error('[Hub] Fatal error:', error);
process.exit(1);
});
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 2a5cdf109495845349f291dc26520be0
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/hub-server.js
uploadId: 920982
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: aee1b8376192c41a6bac6dca7574b820
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/index-essential.js
uploadId: 920982
@@ -0,0 +1,906 @@
/**
* Synaptic AI Pro - Token SuperSave Mode
*
* Experimental: Only 6 meta-tools for 99% context reduction.
* Compatible with all MCP clients without dynamic tool loading support.
*
* Tools:
* 1. list_categories() - Show available tool categories
* 2. list_tools(category) - Show tools in a category with their schemas
* 3. execute(tool_name, params) - Run any tool by name
* 4. inspect(target, ...) - Dynamically inspect objects/components
* 5. modify(gameObject, component, properties) - Dynamically modify properties
* 6. create(type, ...) - Create objects, prefabs, components
*/
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const cors = require('cors');
const { z } = require('zod');
const { createServer } = require('./mcp-server');
const fs = require('fs');
const path = require('path');
const os = require('os');
const app = express();
app.use(cors());
app.use(express.json());
const server = http.createServer(app);
let wss = null;
let unityWebSocket = null;
let mcpServer = null;
// =====================================================
// Load Tool Registry from JSON file
// =====================================================
let TOOL_REGISTRY_RAW = {};
let CATEGORIES = {};
let ALL_TOOLS = {};
function loadToolRegistry() {
try {
const registryPath = path.join(__dirname, 'tool-registry.json');
const data = fs.readFileSync(registryPath, 'utf8');
TOOL_REGISTRY_RAW = JSON.parse(data);
// Build categories from registry
CATEGORIES = {};
ALL_TOOLS = {};
for (const [toolName, toolData] of Object.entries(TOOL_REGISTRY_RAW)) {
const category = (toolData.category || 'Other').toLowerCase();
if (!CATEGORIES[category]) {
CATEGORIES[category] = {
description: `${toolData.category} tools`,
tools: {}
};
}
// Store tool info (use clean name without unity_ prefix as primary key)
const cleanName = toolName.replace(/^unity_/, '');
CATEGORIES[category].tools[cleanName] = {
fullName: toolName,
title: toolData.title,
description: toolData.description
};
// Only store clean name to avoid duplication
ALL_TOOLS[cleanName] = {
fullName: toolName,
category: category,
title: toolData.title,
description: toolData.description
};
}
console.error(`[SuperSave] Loaded ${Object.keys(TOOL_REGISTRY_RAW).length} tools from tool-registry.json`);
} catch (err) {
console.error('[SuperSave] Failed to load tool-registry.json:', err.message);
}
}
// Load on startup
loadToolRegistry();
// =====================================================
// WebSocket Setup (same as index.js)
// =====================================================
function setupWebSocketHandlers() {
if (!wss) return;
// Surface server-level errors (ECONNRESET etc.) that would otherwise
// disappear into the void.
wss.on('error', (err) => {
console.error('[SuperSave] WSS error:', err && err.message ? err.message : err);
});
wss.on('connection', (ws, req) => {
const isUnity = req.headers['x-client-type'] === 'unity' || req.url === '/unity';
// Per-socket error handler — without this, send() failures (peer gone,
// backpressure, etc.) crash silently and the caller just hits the 60s
// timeout with no clue why.
ws.on('error', (err) => {
console.error('[SuperSave] WS socket error:', err && err.message ? err.message : err);
});
if (isUnity || !req.url.includes('mcp')) {
if (unityWebSocket) {
unityWebSocket.close();
}
unityWebSocket = ws;
ws.on('message', async (message) => {
try {
const data = JSON.parse(message);
// Handle shutdown request from new process trying to take over
if (data.type === 'shutdown') {
console.error('[SuperSave] Received shutdown request from new process, shutting down...');
shutdownServer();
return;
}
const responseId = data.id || data.operationId;
if ((data.type === 'operation_result' || data.type === 'operation_response') && responseId) {
const numericId = typeof responseId === 'string' ? parseInt(responseId) : responseId;
if (pendingRequests.has(numericId)) {
const { resolve, reject, timeout } = pendingRequests.get(numericId);
clearTimeout(timeout);
pendingRequests.delete(numericId);
if (data.success !== false) {
resolve(data.content || data.result);
} else {
reject(new Error(data.content || data.error || 'Unity command failed'));
}
}
}
} catch (e) {}
});
ws.on('close', () => {
unityWebSocket = null;
});
}
});
}
// Unity command helper
const pendingRequests = new Map();
let requestId = 0;
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function sendUnityCommandOnce(command, params, id) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
pendingRequests.delete(id);
reject(new Error('timeout'));
}, 60000);
pendingRequests.set(id, { resolve, reject, timeout });
const message = JSON.stringify({
type: 'unity_operation',
command: command,
parameters: {
...params,
operationId: id.toString()
}
});
// Confirm the socket is open before pushing — without this we have
// raced an in-flight close() and the message disappears.
if (!unityWebSocket || unityWebSocket.readyState !== 1 /* OPEN */) {
clearTimeout(timeout);
pendingRequests.delete(id);
const state = unityWebSocket ? unityWebSocket.readyState : 'null';
return reject(new Error(`unityWebSocket not open (readyState=${state})`));
}
// send() with callback so write failures surface immediately instead
// of bleeding into the 60s timeout. ESC-0102 root-cause hunt.
unityWebSocket.send(message, (err) => {
if (err) {
clearTimeout(timeout);
pendingRequests.delete(id);
console.error('[SuperSave] send failed:', err.message);
reject(err);
}
});
});
}
async function sendUnityCommand(command, params = {}) {
const MAX_RETRIES = 30;
const RETRY_DELAY = 10000;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
if (!unityWebSocket || unityWebSocket.readyState !== WebSocket.OPEN) {
if (attempt < MAX_RETRIES) {
console.error(`[SuperSave] Unity not connected (attempt ${attempt}/${MAX_RETRIES}). Waiting...`);
await sleep(RETRY_DELAY);
continue;
}
throw new Error('Unity not connected');
}
const id = ++requestId;
try {
const result = await sendUnityCommandOnce(command, params, id);
return result;
} catch (error) {
if (attempt < MAX_RETRIES) {
console.error(`[SuperSave] Command failed (attempt ${attempt}): ${error.message}`);
await sleep(RETRY_DELAY);
}
}
}
throw new Error('Unity not connected after retries');
}
// =====================================================
// MCP Server with 3 Meta-Tools
// =====================================================
async function setupMCPServer() {
mcpServer = createServer();
// ===== META-TOOL 1: list_categories =====
mcpServer.registerTool('list_categories', {
title: 'List Tool Categories',
description: 'List all available tool categories. Use this first to discover what tools are available.',
inputSchema: z.object({})
}, async () => {
const categories = Object.entries(CATEGORIES).map(([name, data]) => ({
name,
description: data.description,
toolCount: Object.keys(data.tools).length
}));
const totalTools = Object.keys(TOOL_REGISTRY_RAW).length;
return {
content: [{
type: 'text',
text: `Available Categories (${categories.length} categories, ${totalTools} total tools):\n\n` +
categories.map(c => `${c.name} (${c.toolCount} tools)\n ${c.description}`).join('\n\n') +
'\n\nUse list_tools(category) to see tools in a specific category.'
}]
};
});
// ===== META-TOOL 2: list_tools =====
mcpServer.registerTool('list_tools', {
title: 'List Tools in Category',
description: 'List all tools in a specific category with their parameters. Use this to learn how to use specific tools.',
inputSchema: z.object({
category: z.string().describe('Category name (e.g., "gameobject", "material", "lighting")')
})
}, async (params) => {
const category = params.category.toLowerCase();
if (!CATEGORIES[category]) {
const availableCategories = Object.keys(CATEGORIES).join(', ');
return {
content: [{
type: 'text',
text: `Unknown category: "${category}"\n\nAvailable categories: ${availableCategories}`
}]
};
}
const categoryData = CATEGORIES[category];
const tools = Object.entries(categoryData.tools).map(([name, data]) => {
return `${name} (${data.fullName})\n ${data.title}\n ${data.description}`;
});
return {
content: [{
type: 'text',
text: `Category: ${category}\n${categoryData.description}\n\nTools (${tools.length}):\n\n${tools.join('\n\n')}\n\nUse execute(tool_name, params) to run a tool.`
}]
};
});
// ===== META-TOOL 2.5: search_tools =====
mcpServer.registerTool('search_tools', {
title: 'Search Tools',
description: 'Search for tools by keyword in name, title, or description. Use this when you don\'t know the exact category.',
inputSchema: z.object({
query: z.string().describe('Search keyword (e.g., "material", "camera", "physics")'),
category: z.string().optional().describe('Optional: filter by category'),
limit: z.number().optional().default(20).describe('Max results (default: 20)')
})
}, async (params) => {
const query = (params.query || '').toLowerCase();
const categoryFilter = params.category?.toLowerCase();
const limit = params.limit || 20;
const results = [];
for (const [toolName, toolData] of Object.entries(TOOL_REGISTRY_RAW)) {
const title = toolData.title || toolName;
const description = toolData.description || '';
const category = (toolData.category || 'Other').toLowerCase();
// Category filter
if (categoryFilter && category !== categoryFilter) continue;
// If no query, return all (with limit)
if (!query) {
results.push({ name: toolName, title, description, category: toolData.category, score: 0 });
if (results.length >= limit) break;
continue;
}
// Calculate score
let score = 0;
if (toolName.toLowerCase().includes(query)) score += 100;
if (title.toLowerCase().includes(query)) score += 80;
if (description.toLowerCase().includes(query)) score += 40;
if (score > 0) {
results.push({ name: toolName, title, description, category: toolData.category, score });
}
}
// Sort by score and limit
const sorted = results
.sort((a, b) => b.score - a.score)
.slice(0, limit);
if (sorted.length === 0) {
return {
content: [{
type: 'text',
text: `No tools found for query: "${params.query}"\n\nTry different keywords or use list_categories to see available categories.`
}]
};
}
const toolList = sorted.map(t => `${t.name}\n ${t.title} [${t.category}]\n ${t.description.slice(0, 100)}${t.description.length > 100 ? '...' : ''}`);
return {
content: [{
type: 'text',
text: `Found ${sorted.length} tools for "${params.query}":\n\n${toolList.join('\n\n')}\n\nUse execute(tool_name, params) to run a tool.`
}]
};
});
// ===== META-TOOL 2.6: get_tools_reference =====
mcpServer.registerTool('get_tools_reference', {
title: 'Get Full Tools Reference',
description: 'Get complete reference of ALL tools in Markdown format. Use this once at the start of a session to have full tool knowledge without repeated search_tools calls. Saves tokens by eliminating multiple tool discovery calls.',
inputSchema: z.object({
lang: z.enum(['en', 'jp']).optional().default('en').describe('Language for descriptions (en/jp)'),
category: z.string().optional().describe('Optional: filter by specific category'),
format: z.enum(['markdown', 'compact']).optional().default('markdown').describe('Output format: markdown (detailed) or compact (name + description only)')
})
}, async (params) => {
const lang = params.lang || 'en';
const categoryFilter = params.category?.toLowerCase();
const format = params.format || 'markdown';
// Group tools by category
const byCategory = {};
for (const [toolName, toolData] of Object.entries(TOOL_REGISTRY_RAW)) {
const category = (toolData.category || 'Other');
const categoryLower = category.toLowerCase();
if (categoryFilter && categoryLower !== categoryFilter) continue;
if (!byCategory[category]) {
byCategory[category] = [];
}
byCategory[category].push({ name: toolName, ...toolData });
}
const totalTools = Object.values(byCategory).reduce((sum, tools) => sum + tools.length, 0);
let output = '';
if (format === 'markdown') {
output = `# Synaptic AI Pro - Tools Reference\n`;
output += `Total: ${totalTools} tools in ${Object.keys(byCategory).length} categories\n\n`;
for (const [category, tools] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {
output += `## ${category} (${tools.length} tools)\n\n`;
for (const tool of tools.sort((a, b) => a.name.localeCompare(b.name))) {
output += `### ${tool.name}\n`;
output += `${tool.description || tool.title || ''}\n`;
// Add inputSchema info if available
if (tool.inputSchema?.properties) {
const props = Object.entries(tool.inputSchema.properties);
if (props.length > 0) {
output += `**Parameters:**\n`;
for (const [propName, propData] of props) {
const required = tool.inputSchema.required?.includes(propName) ? ' (required)' : '';
const type = propData.type || 'any';
const desc = propData.description || '';
output += `- \`${propName}\`: ${type}${required} - ${desc}\n`;
}
}
}
output += '\n';
}
}
} else {
// Compact format
output = `# Tools Reference (${totalTools} tools)\n\n`;
for (const [category, tools] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {
output += `## ${category}\n`;
for (const tool of tools.sort((a, b) => a.name.localeCompare(b.name))) {
output += `- ${tool.name}: ${(tool.description || tool.title || '').slice(0, 80)}\n`;
}
output += '\n';
}
}
output += `\n---\nUse execute(tool_name, params) to run any tool.`;
return {
content: [{
type: 'text',
text: output
}]
};
});
// ===== MCP RESOURCES: Tools Reference =====
// Generate tools reference markdown from tool-registry.json
function generateToolsReference(format = 'compact') {
const byCategory = {};
for (const [toolName, toolData] of Object.entries(TOOL_REGISTRY_RAW)) {
const category = (toolData.category || 'Other');
if (!byCategory[category]) {
byCategory[category] = [];
}
byCategory[category].push({ name: toolName, ...toolData });
}
const totalTools = Object.values(byCategory).reduce((sum, tools) => sum + tools.length, 0);
let output = '';
if (format === 'markdown') {
output = `# Synaptic AI Pro - Tools Reference\n`;
output += `Total: ${totalTools} tools in ${Object.keys(byCategory).length} categories\n\n`;
for (const [category, tools] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {
output += `## ${category} (${tools.length} tools)\n\n`;
for (const tool of tools.sort((a, b) => a.name.localeCompare(b.name))) {
output += `### ${tool.name}\n`;
output += `${tool.description || tool.title || ''}\n`;
if (tool.inputSchema?.properties) {
const props = Object.entries(tool.inputSchema.properties);
if (props.length > 0) {
output += `**Parameters:**\n`;
for (const [propName, propData] of props) {
const required = tool.inputSchema.required?.includes(propName) ? ' (required)' : '';
const type = propData.type || 'any';
const desc = propData.description || '';
output += `- \`${propName}\`: ${type}${required} - ${desc}\n`;
}
}
}
output += '\n';
}
}
} else {
// Compact format
output = `# Tools Reference (${totalTools} tools)\n\n`;
for (const [category, tools] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {
output += `## ${category}\n`;
for (const tool of tools.sort((a, b) => a.name.localeCompare(b.name))) {
output += `- ${tool.name}: ${(tool.description || tool.title || '').slice(0, 80)}\n`;
}
output += '\n';
}
}
output += `\n---\nUse execute(tool_name, params) to run any tool.`;
return output;
}
// Register MCP Resources for tools reference (for prompt caching)
mcpServer.registerResource('synaptic://tools/reference', {
title: 'Tools Reference (Compact)',
description: 'Complete list of all Unity tools in compact format. Add to context for efficient tool discovery.',
mimeType: 'text/markdown'
}, async () => {
return {
contents: [{
uri: 'synaptic://tools/reference',
mimeType: 'text/markdown',
text: generateToolsReference('compact')
}]
};
});
mcpServer.registerResource('synaptic://tools/reference/full', {
title: 'Tools Reference (Full)',
description: 'Complete list of all Unity tools with full parameter details.',
mimeType: 'text/markdown'
}, async () => {
return {
contents: [{
uri: 'synaptic://tools/reference/full',
mimeType: 'text/markdown',
text: generateToolsReference('markdown')
}]
};
});
// ===== META-TOOL 3: execute =====
mcpServer.registerTool('execute', {
title: 'Execute Tool',
description: 'Execute any Unity tool by name. Use list_tools(category) first to see available tools and their parameters.',
inputSchema: z.object({
tool: z.string().describe('Tool name to execute (e.g., "create_gameobject", "set_transform")'),
params: z.any().optional().describe('Parameters as JSON object {"name":"value"}')
})
}, async (params) => {
const toolName = params.tool;
let toolParams = params.params || {};
// Handle case where params is passed as string (e.g., '{"name":"x"}')
if (typeof toolParams === 'string') {
try {
toolParams = JSON.parse(toolParams);
} catch (e) {
// Try to parse key=value format (e.g., 'name=MyCube')
const keyValueMatch = toolParams.match(/^(\w+)=(.+)$/);
if (keyValueMatch) {
toolParams = { [keyValueMatch[1]]: keyValueMatch[2] };
} else {
// Treat as single value if it's just a plain string
toolParams = {};
}
}
}
// Normalize tool name - strip unity_ prefix if present
const strippedName = toolName.startsWith('unity_') ? toolName.substring(6) : toolName;
const fullName = `unity_${strippedName}`;
// Check if tool exists in registry
const toolInfo = ALL_TOOLS[strippedName];
if (!toolInfo) {
// Find similar tool names for helpful error message
const allToolNames = Object.keys(ALL_TOOLS);
const similar = allToolNames.filter(t =>
t.includes(strippedName) || strippedName.includes(t) ||
t.split('_').some(part => strippedName.includes(part))
).slice(0, 5);
let errorMsg = `Unknown tool: "${toolName}"`;
if (similar.length > 0) {
errorMsg += `\n\nDid you mean: ${similar.join(', ')}?`;
}
errorMsg += `\n\nUse list_categories() to see available categories, then list_tools(category) to see tools.`;
return {
content: [{
type: 'text',
text: errorMsg
}]
};
}
// Get the command name (without unity_ prefix) for Unity
// Use lowercase - Unity's ConvertCommandToOperationType expects lowercase
const commandName = strippedName.toLowerCase();
try {
const result = await sendUnityCommand(commandName, toolParams);
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}]
};
} catch (error) {
// Detailed error message
let errorDetail = `Error executing "${strippedName}":\n`;
errorDetail += ` Message: ${error.message}\n`;
if (error.message.includes('not connected')) {
errorDetail += `\nTroubleshooting:\n`;
errorDetail += ` 1. Check Unity Editor is running\n`;
errorDetail += ` 2. Verify Synaptic AI Pro is connected (check Console)\n`;
errorDetail += ` 3. Try restarting the MCP server\n`;
} else if (error.message.includes('timeout')) {
errorDetail += `\nThe command timed out. Unity may be:\n`;
errorDetail += ` - Compiling scripts\n`;
errorDetail += ` - Processing a heavy operation\n`;
errorDetail += ` - Not responding\n`;
}
errorDetail += `\nTool info: ${toolInfo.title} (${toolInfo.category})`;
return {
content: [{
type: 'text',
text: errorDetail
}]
};
}
});
// ===== META-TOOL 3.5: run_csharp =====
// Arbitrary C# execution escape-hatch (equivalent of Blender's run_python).
// Promoted to a top-level meta-tool so small local LLMs don't have to go
// through the execute({tool, params}) two-level nest.
mcpServer.registerTool('run_csharp', {
title: 'Run C# Code',
description: 'Execute arbitrary C# code against the running Unity Editor (equivalent of Blender run_python). UnityEngine / UnityEditor / System.Linq / Newtonsoft.Json are pre-imported. Use this when no dedicated tool covers the operation. Does NOT trigger AssemblyReload so the connection stays alive.\n\nReturn value: end the snippet with `return X;` to capture X into the `result` field (prefix statements like `var x = ...;` execute first). A bare expression without trailing `;` is also accepted. Side-effect-only snippets return `result: null`. Debug.Log / LogWarning / LogError are captured into `output`.\n\nWORKS: GameObject / Transform / Component manipulation, AssetDatabase, Selection, EditorApplication, scene/asset queries via `FindObjectsByType<T>()` and other generic METHODS, generic method extension calls (`GetComponent<T>()`), arrays + foreach + LINQ-like loops, string interpolation, math, multi-statement bodies.\n\nKNOWN LIMITATION: Unity Mono.CSharp interactive parser cannot instantiate generic TYPES — `new List<int>()`, `new Dictionary<K,V>()`, `new HashSet<T>()` etc. silently return `result: null`. Workarounds: use plain arrays (`new int[] {1,2,3}`), `System.Collections.ArrayList`, or invoke generic helper methods that already exist (e.g. `FindObjectsByType<GameObject>(...)`). Generic method calls themselves are fine; only `new T<U>()` is blocked. LINQ chains that infer `IEnumerable<T>` may also fail — use `foreach` instead.',
inputSchema: z.object({
code: z.string().describe('C# code. End with `return X;` to capture X into `result`. A bare expression (no trailing `;`) also returns its value. Pre-imported: System, System.Linq, System.Collections.Generic, UnityEngine, UnityEditor, Newtonsoft.Json. AVOID `new List<int>()` / `new Dictionary<K,V>()` style generic instantiation (Mono parser limitation) — use arrays or ArrayList instead.')
})
}, async (params) => {
try {
const result = await sendUnityCommand('run_csharp', params);
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `run_csharp failed: ${error.message}`
}]
};
}
});
// ===== META-TOOL 4: inspect =====
mcpServer.registerTool('inspect', {
title: 'Inspect Unity Objects',
description: 'Dynamically inspect any Unity object, component, scene, or project assets. Use this to discover what properties are available before modifying them.',
inputSchema: z.object({
target: z.enum(['gameobject', 'component', 'scene', 'project', 'prefabs', 'hierarchy'])
.describe('What to inspect: gameobject (properties/components), component (all serialized fields), scene (current scene info), project (project structure), prefabs (search prefabs), hierarchy (scene hierarchy)'),
name: z.string().optional().describe('GameObject name (for gameobject/component targets)'),
component: z.string().optional().describe('Component type to inspect (e.g., "Camera", "CinemachineVirtualCamera")'),
path: z.string().optional().describe('Asset path filter for project/prefabs (e.g., "Assets/Prefabs/*")'),
depth: z.number().optional().default(2).describe('Hierarchy depth for nested inspection (default: 2)')
})
}, async (params) => {
try {
const result = await sendUnityCommand('dynamic_inspect', params);
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Inspect failed: ${error.message}\n\nTips:\n- For gameobject: provide "name"\n- For component: provide "name" and "component"\n- For prefabs: provide "path" with wildcards (e.g., "Assets/**/*.prefab")`
}]
};
}
});
// ===== META-TOOL 5: modify =====
mcpServer.registerTool('modify', {
title: 'Modify Unity Objects',
description: 'Dynamically modify any property of a Unity component using property paths. Use inspect() first to discover available properties.',
inputSchema: z.object({
gameObject: z.string().describe('GameObject name'),
component: z.string().describe('Component type (e.g., "Transform", "Camera", "CinemachineVirtualCamera")'),
properties: z.record(z.any()).describe('Property paths and values as key-value pairs (e.g., {"m_Lens.FieldOfView": 60, "m_Priority": 10})'),
createIfMissing: z.boolean().optional().default(false).describe('Add component if it does not exist')
})
}, async (params) => {
try {
const result = await sendUnityCommand('dynamic_modify', params);
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Modify failed: ${error.message}\n\nTips:\n- Use inspect(target:"component") first to see available property paths\n- Nested properties use dot notation: "m_Lens.FieldOfView"\n- Array elements: "m_Materials.Array.data[0]"`
}]
};
}
});
// ===== META-TOOL 6: create =====
mcpServer.registerTool('create', {
title: 'Create Unity Objects',
description: 'Create GameObjects, instantiate prefabs, load scenes, or add components. Universal creation tool.',
inputSchema: z.object({
type: z.enum(['gameobject', 'prefab', 'scene', 'component'])
.describe('What to create: gameobject (empty or primitive), prefab (instantiate from asset), scene (load scene), component (add to existing object)'),
// For gameobject
name: z.string().optional().describe('Name for new GameObject'),
primitive: z.enum(['empty', 'cube', 'sphere', 'cylinder', 'plane', 'capsule', 'quad']).optional().describe('Primitive type (for gameobject)'),
// For prefab
asset: z.string().optional().describe('Asset path for prefab (e.g., "Assets/Prefabs/Enemy.prefab")'),
// For scene
scene: z.string().optional().describe('Scene name or path to load'),
additive: z.boolean().optional().default(false).describe('Load scene additively (for scene type)'),
// For component
gameObject: z.string().optional().describe('Target GameObject (for component type)'),
component: z.string().optional().describe('Component type to add (for component type)'),
// Common
parent: z.string().optional().describe('Parent GameObject name'),
position: z.object({ x: z.number(), y: z.number(), z: z.number() }).optional().describe('World position'),
rotation: z.object({ x: z.number(), y: z.number(), z: z.number() }).optional().describe('Euler rotation'),
scale: z.object({ x: z.number(), y: z.number(), z: z.number() }).optional().describe('Local scale')
})
}, async (params) => {
try {
const result = await sendUnityCommand('dynamic_create', params);
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Create failed: ${error.message}\n\nExamples:\n- GameObject: {type:"gameobject", name:"Player", primitive:"capsule"}\n- Prefab: {type:"prefab", asset:"Assets/Prefabs/Enemy.prefab", position:{x:0,y:0,z:0}}\n- Scene: {type:"scene", scene:"Level2", additive:true}\n- Component: {type:"component", gameObject:"Player", component:"Rigidbody"}`
}]
};
}
});
// Start MCP server
await mcpServer.start();
}
// =====================================================
// Main Entry Point
// =====================================================
// Send shutdown request to prior process on same port
async function requestShutdownFromPriorProcess(port) {
return new Promise((resolve) => {
try {
const ws = new WebSocket(`ws://localhost:${port}`);
const timeout = setTimeout(() => {
ws.close();
resolve(false);
}, 2000);
ws.on('open', () => {
ws.send(JSON.stringify({ type: 'shutdown' }));
clearTimeout(timeout);
setTimeout(() => {
ws.close();
resolve(true);
}, 500);
});
ws.on('error', () => {
clearTimeout(timeout);
resolve(false);
});
} catch (e) {
resolve(false);
}
});
}
// Try to start server with retry logic
function startServerWithRetry(port, maxRetries = 5, retryDelay = 1000) {
return new Promise((resolve, reject) => {
let attempt = 0;
const tryListen = () => {
attempt++;
console.error(`[SuperSave] Attempting to listen on port ${port} (attempt ${attempt}/${maxRetries})`);
const onError = async (err) => {
server.removeListener('error', onError);
if (err.code === 'EADDRINUSE') {
console.error(`[SuperSave] Port ${port} in use`);
// Check if existing server is healthy (another Claude session may be using it)
try {
const http = require('http');
const healthCheck = await new Promise((res, rej) => {
const req = http.get(`http://localhost:${port}/health`, { timeout: 2000 }, (resp) => {
let data = '';
resp.on('data', chunk => data += chunk);
resp.on('end', () => res(data));
});
req.on('error', rej);
req.on('timeout', () => { req.destroy(); rej(new Error('timeout')); });
});
// Existing server is healthy - don't kill it, just skip server binding
console.error(`[SuperSave] Existing healthy server found on port ${port} - sharing connection`);
resolve();
return;
} catch (e) {
// Existing server is dead - safe to take over
}
if (attempt === 1) {
// First retry: try to shutdown prior process
console.error(`[SuperSave] Sending shutdown request to prior process...`);
await requestShutdownFromPriorProcess(port);
}
if (attempt < maxRetries) {
console.error(`[SuperSave] Retrying in ${retryDelay}ms...`);
setTimeout(tryListen, retryDelay);
} else {
reject(new Error(`Failed to bind to port ${port} after ${maxRetries} attempts`));
}
} else {
reject(err);
}
};
server.once('error', onError);
server.listen(port, () => {
server.removeListener('error', onError);
console.error(`[SuperSave] Token SuperSave Mode started on port ${port}`);
console.error(`[SuperSave] Only 6 meta-tools loaded (99% context reduction)`);
resolve();
});
};
tryListen();
});
}
async function main() {
const PORT = process.env.PORT || 8090;
// Start WebSocket server
wss = new WebSocket.Server({ server });
setupWebSocketHandlers();
// Setup and start MCP
await setupMCPServer();
// Start HTTP server with retry logic for EADDRINUSE
await startServerWithRetry(PORT);
}
// Shutdown handler
function shutdownServer() {
if (unityWebSocket && unityWebSocket.readyState === WebSocket.OPEN) {
unityWebSocket.close();
}
if (wss) {
wss.close();
}
if (server && server.listening) {
server.close(() => {
process.exit(0);
});
} else {
process.exit(0);
}
setTimeout(() => {
process.exit(1);
}, 5000);
}
process.on('SIGINT', shutdownServer);
process.on('SIGTERM', shutdownServer);
process.stdin.on('close', () => {
shutdownServer();
});
process.on('uncaughtException', (error) => {
// Log EADDRINUSE and other critical errors
if (error.code === 'EADDRINUSE') {
console.error(`[SuperSave] uncaughtException: EADDRINUSE - port already in use`);
} else {
console.error(`[SuperSave] uncaughtException: ${error.message}`);
}
});
process.on('unhandledRejection', (reason, promise) => {
console.error(`[SuperSave] unhandledRejection: ${reason}`);
});
main().catch(err => {
console.error('[SuperSave] Fatal error:', err);
process.exit(1);
});
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 23e712a62571842ceb12739ea240a053
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/index-supersave.js
uploadId: 920982
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 0a4175e1c6a18427ba16d7e58e1173fa
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/index.js
uploadId: 920982
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a2e176bd848a84855b6f6c338cf3fcfc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,36 @@
[HTTP] Loaded 356 tools in 32 categories
[HTTP] WebSocket server attached to HTTP server (port 8086)
╔═══════════════════════════════════════════════════════════╗
║ Synaptic AI Pro - HTTP Server ║
╠═══════════════════════════════════════════════════════════╣
║ Port: 8086 ║
║ HTTP: http://localhost:8086 ║
║ WebSocket: ws://localhost:8086 ║
╠═══════════════════════════════════════════════════════════╣
║ Endpoints: ║
║ GET / - AI prompt + tools reference ║
║ GET /health - Connection status ║
║ GET /tools - Full tool registry ║
║ GET /categories - Tool categories ║
║ POST /execute - Execute tool ║
║ POST /batch - Batch execute ║
╠═══════════════════════════════════════════════════════════╣
║ Waiting for Unity to connect... ║
╚═══════════════════════════════════════════════════════════╝
[HTTP] Unity connected via WebSocket
[HTTP] Unity disconnected
[HTTP] Unity connected via WebSocket
[HTTP] Unity disconnected
[HTTP] Unity connected via WebSocket
[HTTP] Unity disconnected
[HTTP] Unity connected via WebSocket
[HTTP] Unity disconnected
[HTTP] Unity connected via WebSocket
[HTTP] Unity disconnected
[HTTP] Unity connected via WebSocket
[HTTP] Unity disconnected
[HTTP] Unity connected via WebSocket
[HTTP] Unity disconnected
[HTTP] Unity connected via WebSocket
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: fc161d8b6d08c4be39e63293cf312839
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/logs/http-server.log
uploadId: 920982
@@ -0,0 +1,23 @@
{
"servers": {
"unity": {
"command": "node",
"args": ["index.js"],
"cwd": "/Users/macbookpro/Desktop/moveのコピー/Assets/Synaptic AI Pro/MCPServer",
"env": {
"NODE_ENV": "production"
}
}
},
"tools": {
"unity_create": {
"description": "Create Unity GameObjects"
},
"unity_modify": {
"description": "Modify Unity GameObjects"
},
"unity_delete": {
"description": "Delete Unity GameObjects"
}
}
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: b494213945eca4da4bcf6b6e77856348
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/mcp-config.json
uploadId: 920982
@@ -0,0 +1,195 @@
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
const {
ListToolsRequestSchema,
CallToolRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema
} = require('@modelcontextprotocol/sdk/types.js');
// Zodスキーマを MCP JSON Schema に変換
function convertZodSchemaToMCP(zodSchema) {
if (!zodSchema || !zodSchema._def) return {};
const def = zodSchema._def;
if (def.typeName === 'ZodObject') {
const properties = {};
const required = [];
for (const [key, value] of Object.entries(def.shape())) {
properties[key] = convertZodSchemaToMCP(value);
if (!value.isOptional()) {
required.push(key);
}
}
return {
type: 'object',
properties,
required: required.length > 0 ? required : undefined
};
}
if (def.typeName === 'ZodString') {
return { type: 'string' };
}
if (def.typeName === 'ZodNumber') {
return { type: 'number' };
}
if (def.typeName === 'ZodBoolean') {
return { type: 'boolean' };
}
if (def.typeName === 'ZodArray') {
return {
type: 'array',
items: convertZodSchemaToMCP(def.type)
};
}
if (def.typeName === 'ZodEnum') {
return {
type: 'string',
enum: def.values
};
}
if (def.typeName === 'ZodOptional') {
return convertZodSchemaToMCP(def.innerType);
}
if (def.typeName === 'ZodDefault') {
const schema = convertZodSchemaToMCP(def.innerType);
schema.default = def.defaultValue();
return schema;
}
if (def.typeName === 'ZodAny') {
return {}; // Any type - no restrictions
}
if (def.typeName === 'ZodRecord') {
return { type: 'object' };
}
return { type: 'string' };
}
function createServer() {
const registeredTools = {};
const registeredResources = {};
const server = new Server(
{
name: 'unity-mcp',
version: '1.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
// ツール一覧を返すハンドラー
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: Object.entries(registeredTools).map(([name, tool]) => ({
name,
description: tool.description,
inputSchema: tool.inputSchema
}))
};
});
// ツール実行ハンドラー
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (!registeredTools[name]) {
throw new Error(`Unknown tool: ${name}`);
}
const tool = registeredTools[name];
try {
// Zodでバリデーション
if (tool.zodSchema) {
tool.zodSchema.parse(args);
}
const result = await tool.handler(args);
return result;
} catch (error) {
// エラーログを標準エラー出力に出さない(JSON-RPC通信を妨害するため)
throw error;
}
});
// リソース一覧を返すハンドラー
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: Object.entries(registeredResources).map(([uri, resource]) => ({
uri,
name: resource.name,
description: resource.description,
mimeType: resource.mimeType
}))
};
});
// リソース読み取りハンドラー
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (!registeredResources[uri]) {
throw new Error(`Unknown resource: ${uri}`);
}
const resource = registeredResources[uri];
const result = await resource.handler();
return result;
});
// ツール登録メソッド
server.registerTool = function(name, config, handler) {
const inputSchema = convertZodSchemaToMCP(config.inputSchema);
registeredTools[name] = {
description: config.description || config.title,
inputSchema,
zodSchema: config.inputSchema,
handler
};
// ログを標準エラー出力に出さない
};
// リソース登録メソッド
server.registerResource = function(uri, config, handler) {
registeredResources[uri] = {
name: config.title,
description: config.description,
mimeType: config.mimeType,
handler
};
// ログを標準エラー出力に出さない
};
// サーバー起動メソッド
server.start = async function() {
const transport = new StdioServerTransport();
await this.connect(transport);
// ログを標準エラー出力に出さない
};
return server;
}
module.exports = { createServer };
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 43fade75c56ab4e34beb33a912039d45
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/mcp-server.js
uploadId: 920982
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ce50d81868b4f40c5817810a0124a078
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 543db41d8b038498aa18932b924a0295
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 948f0b8ef1e4f4e5ca74a34c75a825a6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Anthropic, PBC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 5cbd0eee6fc2748f298671ce5fadb904
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/LICENSE
uploadId: 920982
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 6b975db0cbec74ce79d47d73a78c98a3
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/README.md
uploadId: 920982
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 533872ecd915544f18b92f579e30bc8b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d54b70b24465143f5b696c3ccdaf955b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: f88e9ba25df914806b3ddd2103f58ea8
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/cli.d.ts.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 306bfe69a7d624b5a8aff58a1b04c284
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/cli.d.ts
uploadId: 920982
@@ -0,0 +1,135 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ws_1 = __importDefault(require("ws"));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
global.WebSocket = ws_1.default;
const express_1 = __importDefault(require("express"));
const index_js_1 = require("./client/index.js");
const sse_js_1 = require("./client/sse.js");
const stdio_js_1 = require("./client/stdio.js");
const websocket_js_1 = require("./client/websocket.js");
const index_js_2 = require("./server/index.js");
const sse_js_2 = require("./server/sse.js");
const stdio_js_2 = require("./server/stdio.js");
const types_js_1 = require("./types.js");
async function runClient(url_or_command, args) {
const client = new index_js_1.Client({
name: "mcp-typescript test client",
version: "0.1.0",
}, {
capabilities: {
sampling: {},
},
});
let clientTransport;
let url = undefined;
try {
url = new URL(url_or_command);
}
catch (_a) {
// Ignore
}
if ((url === null || url === void 0 ? void 0 : url.protocol) === "http:" || (url === null || url === void 0 ? void 0 : url.protocol) === "https:") {
clientTransport = new sse_js_1.SSEClientTransport(new URL(url_or_command));
}
else if ((url === null || url === void 0 ? void 0 : url.protocol) === "ws:" || (url === null || url === void 0 ? void 0 : url.protocol) === "wss:") {
clientTransport = new websocket_js_1.WebSocketClientTransport(new URL(url_or_command));
}
else {
clientTransport = new stdio_js_1.StdioClientTransport({
command: url_or_command,
args,
});
}
console.log("Connected to server.");
await client.connect(clientTransport);
console.log("Initialized.");
await client.request({ method: "resources/list" }, types_js_1.ListResourcesResultSchema);
await client.close();
console.log("Closed.");
}
async function runServer(port) {
if (port !== null) {
const app = (0, express_1.default)();
let servers = [];
app.get("/sse", async (req, res) => {
console.log("Got new SSE connection");
const transport = new sse_js_2.SSEServerTransport("/message", res);
const server = new index_js_2.Server({
name: "mcp-typescript test server",
version: "0.1.0",
}, {
capabilities: {},
});
servers.push(server);
server.onclose = () => {
console.log("SSE connection closed");
servers = servers.filter((s) => s !== server);
};
await server.connect(transport);
});
app.post("/message", async (req, res) => {
console.log("Received message");
const sessionId = req.query.sessionId;
const transport = servers
.map((s) => s.transport)
.find((t) => t.sessionId === sessionId);
if (!transport) {
res.status(404).send("Session not found");
return;
}
await transport.handlePostMessage(req, res);
});
app.listen(port, (error) => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log(`Server running on http://localhost:${port}/sse`);
});
}
else {
const server = new index_js_2.Server({
name: "mcp-typescript test server",
version: "0.1.0",
}, {
capabilities: {
prompts: {},
resources: {},
tools: {},
logging: {},
},
});
const transport = new stdio_js_2.StdioServerTransport();
await server.connect(transport);
console.log("Server running on stdio");
}
}
const args = process.argv.slice(2);
const command = args[0];
switch (command) {
case "client":
if (args.length < 2) {
console.error("Usage: client <server_url_or_command> [args...]");
process.exit(1);
}
runClient(args[1], args.slice(2)).catch((error) => {
console.error(error);
process.exit(1);
});
break;
case "server": {
const port = args[1] ? parseInt(args[1]) : null;
runServer(port).catch((error) => {
console.error(error);
process.exit(1);
});
break;
}
default:
console.error("Unrecognized command:", command);
}
//# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 46398c9eac4ef44bba5c98d5882c1ddd
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/cli.js.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: cb69595fbfc46409bb6ac4f35a09a29f
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/cli.js
uploadId: 920982
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6213b24c68d8846fcb1a93be66a16319
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,248 @@
import { OAuthClientMetadata, OAuthClientInformation, OAuthTokens, OAuthMetadata, OAuthClientInformationFull, OAuthProtectedResourceMetadata, AuthorizationServerMetadata } from "../shared/auth.js";
import { OAuthError } from "../server/auth/errors.js";
import { FetchLike } from "../shared/transport.js";
/**
* Implements an end-to-end OAuth client to be used with one MCP server.
*
* This client relies upon a concept of an authorized "session," the exact
* meaning of which is application-defined. Tokens, authorization codes, and
* code verifiers should not cross different sessions.
*/
export interface OAuthClientProvider {
/**
* The URL to redirect the user agent to after authorization.
*/
get redirectUrl(): string | URL;
/**
* Metadata about this OAuth client.
*/
get clientMetadata(): OAuthClientMetadata;
/**
* Returns a OAuth2 state parameter.
*/
state?(): string | Promise<string>;
/**
* Loads information about this OAuth client, as registered already with the
* server, or returns `undefined` if the client is not registered with the
* server.
*/
clientInformation(): OAuthClientInformation | undefined | Promise<OAuthClientInformation | undefined>;
/**
* If implemented, this permits the OAuth client to dynamically register with
* the server. Client information saved this way should later be read via
* `clientInformation()`.
*
* This method is not required to be implemented if client information is
* statically known (e.g., pre-registered).
*/
saveClientInformation?(clientInformation: OAuthClientInformationFull): void | Promise<void>;
/**
* Loads any existing OAuth tokens for the current session, or returns
* `undefined` if there are no saved tokens.
*/
tokens(): OAuthTokens | undefined | Promise<OAuthTokens | undefined>;
/**
* Stores new OAuth tokens for the current session, after a successful
* authorization.
*/
saveTokens(tokens: OAuthTokens): void | Promise<void>;
/**
* Invoked to redirect the user agent to the given URL to begin the authorization flow.
*/
redirectToAuthorization(authorizationUrl: URL): void | Promise<void>;
/**
* Saves a PKCE code verifier for the current session, before redirecting to
* the authorization flow.
*/
saveCodeVerifier(codeVerifier: string): void | Promise<void>;
/**
* Loads the PKCE code verifier for the current session, necessary to validate
* the authorization result.
*/
codeVerifier(): string | Promise<string>;
/**
* Adds custom client authentication to OAuth token requests.
*
* This optional method allows implementations to customize how client credentials
* are included in token exchange and refresh requests. When provided, this method
* is called instead of the default authentication logic, giving full control over
* the authentication mechanism.
*
* Common use cases include:
* - Supporting authentication methods beyond the standard OAuth 2.0 methods
* - Adding custom headers for proprietary authentication schemes
* - Implementing client assertion-based authentication (e.g., JWT bearer tokens)
*
* @param headers - The request headers (can be modified to add authentication)
* @param params - The request body parameters (can be modified to add credentials)
* @param url - The token endpoint URL being called
* @param metadata - Optional OAuth metadata for the server, which may include supported authentication methods
*/
addClientAuthentication?(headers: Headers, params: URLSearchParams, url: string | URL, metadata?: AuthorizationServerMetadata): void | Promise<void>;
/**
* If defined, overrides the selection and validation of the
* RFC 8707 Resource Indicator. If left undefined, default
* validation behavior will be used.
*
* Implementations must verify the returned resource matches the MCP server.
*/
validateResourceURL?(serverUrl: string | URL, resource?: string): Promise<URL | undefined>;
/**
* If implemented, provides a way for the client to invalidate (e.g. delete) the specified
* credentials, in the case where the server has indicated that they are no longer valid.
* This avoids requiring the user to intervene manually.
*/
invalidateCredentials?(scope: 'all' | 'client' | 'tokens' | 'verifier'): void | Promise<void>;
}
export type AuthResult = "AUTHORIZED" | "REDIRECT";
export declare class UnauthorizedError extends Error {
constructor(message?: string);
}
/**
* Parses an OAuth error response from a string or Response object.
*
* If the input is a standard OAuth2.0 error response, it will be parsed according to the spec
* and an instance of the appropriate OAuthError subclass will be returned.
* If parsing fails, it falls back to a generic ServerError that includes
* the response status (if available) and original content.
*
* @param input - A Response object or string containing the error response
* @returns A Promise that resolves to an OAuthError instance
*/
export declare function parseErrorResponse(input: Response | string): Promise<OAuthError>;
/**
* Orchestrates the full auth flow with a server.
*
* This can be used as a single entry point for all authorization functionality,
* instead of linking together the other lower-level functions in this module.
*/
export declare function auth(provider: OAuthClientProvider, options: {
serverUrl: string | URL;
authorizationCode?: string;
scope?: string;
resourceMetadataUrl?: URL;
fetchFn?: FetchLike;
}): Promise<AuthResult>;
export declare function selectResourceURL(serverUrl: string | URL, provider: OAuthClientProvider, resourceMetadata?: OAuthProtectedResourceMetadata): Promise<URL | undefined>;
/**
* Extract resource_metadata from response header.
*/
export declare function extractResourceMetadataUrl(res: Response): URL | undefined;
/**
* Looks up RFC 9728 OAuth 2.0 Protected Resource Metadata.
*
* If the server returns a 404 for the well-known endpoint, this function will
* return `undefined`. Any other errors will be thrown as exceptions.
*/
export declare function discoverOAuthProtectedResourceMetadata(serverUrl: string | URL, opts?: {
protocolVersion?: string;
resourceMetadataUrl?: string | URL;
}, fetchFn?: FetchLike): Promise<OAuthProtectedResourceMetadata>;
/**
* Looks up RFC 8414 OAuth 2.0 Authorization Server Metadata.
*
* If the server returns a 404 for the well-known endpoint, this function will
* return `undefined`. Any other errors will be thrown as exceptions.
*
* @deprecated This function is deprecated in favor of `discoverAuthorizationServerMetadata`.
*/
export declare function discoverOAuthMetadata(issuer: string | URL, { authorizationServerUrl, protocolVersion, }?: {
authorizationServerUrl?: string | URL;
protocolVersion?: string;
}, fetchFn?: FetchLike): Promise<OAuthMetadata | undefined>;
/**
* Builds a list of discovery URLs to try for authorization server metadata.
* URLs are returned in priority order:
* 1. OAuth metadata at the given URL
* 2. OAuth metadata at root (if URL has path)
* 3. OIDC metadata endpoints
*/
export declare function buildDiscoveryUrls(authorizationServerUrl: string | URL): {
url: URL;
type: 'oauth' | 'oidc';
}[];
/**
* Discovers authorization server metadata with support for RFC 8414 OAuth 2.0 Authorization Server Metadata
* and OpenID Connect Discovery 1.0 specifications.
*
* This function implements a fallback strategy for authorization server discovery:
* 1. Attempts RFC 8414 OAuth metadata discovery first
* 2. If OAuth discovery fails, falls back to OpenID Connect Discovery
*
* @param authorizationServerUrl - The authorization server URL obtained from the MCP Server's
* protected resource metadata, or the MCP server's URL if the
* metadata was not found.
* @param options - Configuration options
* @param options.fetchFn - Optional fetch function for making HTTP requests, defaults to global fetch
* @param options.protocolVersion - MCP protocol version to use, defaults to LATEST_PROTOCOL_VERSION
* @returns Promise resolving to authorization server metadata, or undefined if discovery fails
*/
export declare function discoverAuthorizationServerMetadata(authorizationServerUrl: string | URL, { fetchFn, protocolVersion, }?: {
fetchFn?: FetchLike;
protocolVersion?: string;
}): Promise<AuthorizationServerMetadata | undefined>;
/**
* Begins the authorization flow with the given server, by generating a PKCE challenge and constructing the authorization URL.
*/
export declare function startAuthorization(authorizationServerUrl: string | URL, { metadata, clientInformation, redirectUrl, scope, state, resource, }: {
metadata?: AuthorizationServerMetadata;
clientInformation: OAuthClientInformation;
redirectUrl: string | URL;
scope?: string;
state?: string;
resource?: URL;
}): Promise<{
authorizationUrl: URL;
codeVerifier: string;
}>;
/**
* Exchanges an authorization code for an access token with the given server.
*
* Supports multiple client authentication methods as specified in OAuth 2.1:
* - Automatically selects the best authentication method based on server support
* - Falls back to appropriate defaults when server metadata is unavailable
*
* @param authorizationServerUrl - The authorization server's base URL
* @param options - Configuration object containing client info, auth code, etc.
* @returns Promise resolving to OAuth tokens
* @throws {Error} When token exchange fails or authentication is invalid
*/
export declare function exchangeAuthorization(authorizationServerUrl: string | URL, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn, }: {
metadata?: AuthorizationServerMetadata;
clientInformation: OAuthClientInformation;
authorizationCode: string;
codeVerifier: string;
redirectUri: string | URL;
resource?: URL;
addClientAuthentication?: OAuthClientProvider["addClientAuthentication"];
fetchFn?: FetchLike;
}): Promise<OAuthTokens>;
/**
* Exchange a refresh token for an updated access token.
*
* Supports multiple client authentication methods as specified in OAuth 2.1:
* - Automatically selects the best authentication method based on server support
* - Preserves the original refresh token if a new one is not returned
*
* @param authorizationServerUrl - The authorization server's base URL
* @param options - Configuration object containing client info, refresh token, etc.
* @returns Promise resolving to OAuth tokens (preserves original refresh_token if not replaced)
* @throws {Error} When token refresh fails or authentication is invalid
*/
export declare function refreshAuthorization(authorizationServerUrl: string | URL, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn, }: {
metadata?: AuthorizationServerMetadata;
clientInformation: OAuthClientInformation;
refreshToken: string;
resource?: URL;
addClientAuthentication?: OAuthClientProvider["addClientAuthentication"];
fetchFn?: FetchLike;
}): Promise<OAuthTokens>;
/**
* Performs OAuth 2.0 Dynamic Client Registration according to RFC 7591.
*/
export declare function registerClient(authorizationServerUrl: string | URL, { metadata, clientMetadata, fetchFn, }: {
metadata?: AuthorizationServerMetadata;
clientMetadata: OAuthClientMetadata;
fetchFn?: FetchLike;
}): Promise<OAuthClientInformationFull>;
//# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/client/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 19860691681fa4461944c98806a63d01
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.d.ts.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 53a67187dcef7491f8e4b9fa9982a6a7
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.d.ts
uploadId: 920982
@@ -0,0 +1,718 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnauthorizedError = void 0;
exports.parseErrorResponse = parseErrorResponse;
exports.auth = auth;
exports.selectResourceURL = selectResourceURL;
exports.extractResourceMetadataUrl = extractResourceMetadataUrl;
exports.discoverOAuthProtectedResourceMetadata = discoverOAuthProtectedResourceMetadata;
exports.discoverOAuthMetadata = discoverOAuthMetadata;
exports.buildDiscoveryUrls = buildDiscoveryUrls;
exports.discoverAuthorizationServerMetadata = discoverAuthorizationServerMetadata;
exports.startAuthorization = startAuthorization;
exports.exchangeAuthorization = exchangeAuthorization;
exports.refreshAuthorization = refreshAuthorization;
exports.registerClient = registerClient;
const pkce_challenge_1 = __importDefault(require("pkce-challenge"));
const types_js_1 = require("../types.js");
const auth_js_1 = require("../shared/auth.js");
const auth_js_2 = require("../shared/auth.js");
const auth_utils_js_1 = require("../shared/auth-utils.js");
const errors_js_1 = require("../server/auth/errors.js");
class UnauthorizedError extends Error {
constructor(message) {
super(message !== null && message !== void 0 ? message : "Unauthorized");
}
}
exports.UnauthorizedError = UnauthorizedError;
/**
* Determines the best client authentication method to use based on server support and client configuration.
*
* Priority order (highest to lowest):
* 1. client_secret_basic (if client secret is available)
* 2. client_secret_post (if client secret is available)
* 3. none (for public clients)
*
* @param clientInformation - OAuth client information containing credentials
* @param supportedMethods - Authentication methods supported by the authorization server
* @returns The selected authentication method
*/
function selectClientAuthMethod(clientInformation, supportedMethods) {
const hasClientSecret = clientInformation.client_secret !== undefined;
// If server doesn't specify supported methods, use RFC 6749 defaults
if (supportedMethods.length === 0) {
return hasClientSecret ? "client_secret_post" : "none";
}
// Try methods in priority order (most secure first)
if (hasClientSecret && supportedMethods.includes("client_secret_basic")) {
return "client_secret_basic";
}
if (hasClientSecret && supportedMethods.includes("client_secret_post")) {
return "client_secret_post";
}
if (supportedMethods.includes("none")) {
return "none";
}
// Fallback: use what we have
return hasClientSecret ? "client_secret_post" : "none";
}
/**
* Applies client authentication to the request based on the specified method.
*
* Implements OAuth 2.1 client authentication methods:
* - client_secret_basic: HTTP Basic authentication (RFC 6749 Section 2.3.1)
* - client_secret_post: Credentials in request body (RFC 6749 Section 2.3.1)
* - none: Public client authentication (RFC 6749 Section 2.1)
*
* @param method - The authentication method to use
* @param clientInformation - OAuth client information containing credentials
* @param headers - HTTP headers object to modify
* @param params - URL search parameters to modify
* @throws {Error} When required credentials are missing
*/
function applyClientAuthentication(method, clientInformation, headers, params) {
const { client_id, client_secret } = clientInformation;
switch (method) {
case "client_secret_basic":
applyBasicAuth(client_id, client_secret, headers);
return;
case "client_secret_post":
applyPostAuth(client_id, client_secret, params);
return;
case "none":
applyPublicAuth(client_id, params);
return;
default:
throw new Error(`Unsupported client authentication method: ${method}`);
}
}
/**
* Applies HTTP Basic authentication (RFC 6749 Section 2.3.1)
*/
function applyBasicAuth(clientId, clientSecret, headers) {
if (!clientSecret) {
throw new Error("client_secret_basic authentication requires a client_secret");
}
const credentials = btoa(`${clientId}:${clientSecret}`);
headers.set("Authorization", `Basic ${credentials}`);
}
/**
* Applies POST body authentication (RFC 6749 Section 2.3.1)
*/
function applyPostAuth(clientId, clientSecret, params) {
params.set("client_id", clientId);
if (clientSecret) {
params.set("client_secret", clientSecret);
}
}
/**
* Applies public client authentication (RFC 6749 Section 2.1)
*/
function applyPublicAuth(clientId, params) {
params.set("client_id", clientId);
}
/**
* Parses an OAuth error response from a string or Response object.
*
* If the input is a standard OAuth2.0 error response, it will be parsed according to the spec
* and an instance of the appropriate OAuthError subclass will be returned.
* If parsing fails, it falls back to a generic ServerError that includes
* the response status (if available) and original content.
*
* @param input - A Response object or string containing the error response
* @returns A Promise that resolves to an OAuthError instance
*/
async function parseErrorResponse(input) {
const statusCode = input instanceof Response ? input.status : undefined;
const body = input instanceof Response ? await input.text() : input;
try {
const result = auth_js_1.OAuthErrorResponseSchema.parse(JSON.parse(body));
const { error, error_description, error_uri } = result;
const errorClass = errors_js_1.OAUTH_ERRORS[error] || errors_js_1.ServerError;
return new errorClass(error_description || '', error_uri);
}
catch (error) {
// Not a valid OAuth error response, but try to inform the user of the raw data anyway
const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ''}Invalid OAuth error response: ${error}. Raw body: ${body}`;
return new errors_js_1.ServerError(errorMessage);
}
}
/**
* Orchestrates the full auth flow with a server.
*
* This can be used as a single entry point for all authorization functionality,
* instead of linking together the other lower-level functions in this module.
*/
async function auth(provider, options) {
var _a, _b;
try {
return await authInternal(provider, options);
}
catch (error) {
// Handle recoverable error types by invalidating credentials and retrying
if (error instanceof errors_js_1.InvalidClientError || error instanceof errors_js_1.UnauthorizedClientError) {
await ((_a = provider.invalidateCredentials) === null || _a === void 0 ? void 0 : _a.call(provider, 'all'));
return await authInternal(provider, options);
}
else if (error instanceof errors_js_1.InvalidGrantError) {
await ((_b = provider.invalidateCredentials) === null || _b === void 0 ? void 0 : _b.call(provider, 'tokens'));
return await authInternal(provider, options);
}
// Throw otherwise
throw error;
}
}
async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn, }) {
let resourceMetadata;
let authorizationServerUrl;
try {
resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl }, fetchFn);
if (resourceMetadata.authorization_servers && resourceMetadata.authorization_servers.length > 0) {
authorizationServerUrl = resourceMetadata.authorization_servers[0];
}
}
catch (_a) {
// Ignore errors and fall back to /.well-known/oauth-authorization-server
}
/**
* If we don't get a valid authorization server metadata from protected resource metadata,
* fallback to the legacy MCP spec's implementation (version 2025-03-26): MCP server acts as the Authorization server.
*/
if (!authorizationServerUrl) {
authorizationServerUrl = serverUrl;
}
const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
const metadata = await discoverAuthorizationServerMetadata(authorizationServerUrl, {
fetchFn,
});
// Handle client registration if needed
let clientInformation = await Promise.resolve(provider.clientInformation());
if (!clientInformation) {
if (authorizationCode !== undefined) {
throw new Error("Existing OAuth client information is required when exchanging an authorization code");
}
if (!provider.saveClientInformation) {
throw new Error("OAuth client information must be saveable for dynamic registration");
}
const fullInformation = await registerClient(authorizationServerUrl, {
metadata,
clientMetadata: provider.clientMetadata,
fetchFn,
});
await provider.saveClientInformation(fullInformation);
clientInformation = fullInformation;
}
// Exchange authorization code for tokens
if (authorizationCode !== undefined) {
const codeVerifier = await provider.codeVerifier();
const tokens = await exchangeAuthorization(authorizationServerUrl, {
metadata,
clientInformation,
authorizationCode,
codeVerifier,
redirectUri: provider.redirectUrl,
resource,
addClientAuthentication: provider.addClientAuthentication,
fetchFn: fetchFn,
});
await provider.saveTokens(tokens);
return "AUTHORIZED";
}
const tokens = await provider.tokens();
// Handle token refresh or new authorization
if (tokens === null || tokens === void 0 ? void 0 : tokens.refresh_token) {
try {
// Attempt to refresh the token
const newTokens = await refreshAuthorization(authorizationServerUrl, {
metadata,
clientInformation,
refreshToken: tokens.refresh_token,
resource,
addClientAuthentication: provider.addClientAuthentication,
fetchFn,
});
await provider.saveTokens(newTokens);
return "AUTHORIZED";
}
catch (error) {
// If this is a ServerError, or an unknown type, log it out and try to continue. Otherwise, escalate so we can fix things and retry.
if (!(error instanceof errors_js_1.OAuthError) || error instanceof errors_js_1.ServerError) {
// Could not refresh OAuth tokens
}
else {
// Refresh failed for another reason, re-throw
throw error;
}
}
}
const state = provider.state ? await provider.state() : undefined;
// Start new authorization flow
const { authorizationUrl, codeVerifier } = await startAuthorization(authorizationServerUrl, {
metadata,
clientInformation,
state,
redirectUrl: provider.redirectUrl,
scope: scope || provider.clientMetadata.scope,
resource,
});
await provider.saveCodeVerifier(codeVerifier);
await provider.redirectToAuthorization(authorizationUrl);
return "REDIRECT";
}
async function selectResourceURL(serverUrl, provider, resourceMetadata) {
const defaultResource = (0, auth_utils_js_1.resourceUrlFromServerUrl)(serverUrl);
// If provider has custom validation, delegate to it
if (provider.validateResourceURL) {
return await provider.validateResourceURL(defaultResource, resourceMetadata === null || resourceMetadata === void 0 ? void 0 : resourceMetadata.resource);
}
// Only include resource parameter when Protected Resource Metadata is present
if (!resourceMetadata) {
return undefined;
}
// Validate that the metadata's resource is compatible with our request
if (!(0, auth_utils_js_1.checkResourceAllowed)({ requestedResource: defaultResource, configuredResource: resourceMetadata.resource })) {
throw new Error(`Protected resource ${resourceMetadata.resource} does not match expected ${defaultResource} (or origin)`);
}
// Prefer the resource from metadata since it's what the server is telling us to request
return new URL(resourceMetadata.resource);
}
/**
* Extract resource_metadata from response header.
*/
function extractResourceMetadataUrl(res) {
const authenticateHeader = res.headers.get("WWW-Authenticate");
if (!authenticateHeader) {
return undefined;
}
const [type, scheme] = authenticateHeader.split(' ');
if (type.toLowerCase() !== 'bearer' || !scheme) {
return undefined;
}
const regex = /resource_metadata="([^"]*)"/;
const match = regex.exec(authenticateHeader);
if (!match) {
return undefined;
}
try {
return new URL(match[1]);
}
catch (_a) {
return undefined;
}
}
/**
* Looks up RFC 9728 OAuth 2.0 Protected Resource Metadata.
*
* If the server returns a 404 for the well-known endpoint, this function will
* return `undefined`. Any other errors will be thrown as exceptions.
*/
async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn = fetch) {
const response = await discoverMetadataWithFallback(serverUrl, 'oauth-protected-resource', fetchFn, {
protocolVersion: opts === null || opts === void 0 ? void 0 : opts.protocolVersion,
metadataUrl: opts === null || opts === void 0 ? void 0 : opts.resourceMetadataUrl,
});
if (!response || response.status === 404) {
throw new Error(`Resource server does not implement OAuth 2.0 Protected Resource Metadata.`);
}
if (!response.ok) {
throw new Error(`HTTP ${response.status} trying to load well-known OAuth protected resource metadata.`);
}
return auth_js_2.OAuthProtectedResourceMetadataSchema.parse(await response.json());
}
/**
* Helper function to handle fetch with CORS retry logic
*/
async function fetchWithCorsRetry(url, headers, fetchFn = fetch) {
try {
return await fetchFn(url, { headers });
}
catch (error) {
if (error instanceof TypeError) {
if (headers) {
// CORS errors come back as TypeError, retry without headers
return fetchWithCorsRetry(url, undefined, fetchFn);
}
else {
// We're getting CORS errors on retry too, return undefined
return undefined;
}
}
throw error;
}
}
/**
* Constructs the well-known path for auth-related metadata discovery
*/
function buildWellKnownPath(wellKnownPrefix, pathname = '', options = {}) {
// Strip trailing slash from pathname to avoid double slashes
if (pathname.endsWith('/')) {
pathname = pathname.slice(0, -1);
}
return options.prependPathname
? `${pathname}/.well-known/${wellKnownPrefix}`
: `/.well-known/${wellKnownPrefix}${pathname}`;
}
/**
* Tries to discover OAuth metadata at a specific URL
*/
async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
const headers = {
"MCP-Protocol-Version": protocolVersion
};
return await fetchWithCorsRetry(url, headers, fetchFn);
}
/**
* Determines if fallback to root discovery should be attempted
*/
function shouldAttemptFallback(response, pathname) {
return !response || (response.status >= 400 && response.status < 500) && pathname !== '/';
}
/**
* Generic function for discovering OAuth metadata with fallback support
*/
async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
var _a, _b;
const issuer = new URL(serverUrl);
const protocolVersion = (_a = opts === null || opts === void 0 ? void 0 : opts.protocolVersion) !== null && _a !== void 0 ? _a : types_js_1.LATEST_PROTOCOL_VERSION;
let url;
if (opts === null || opts === void 0 ? void 0 : opts.metadataUrl) {
url = new URL(opts.metadataUrl);
}
else {
// Try path-aware discovery first
const wellKnownPath = buildWellKnownPath(wellKnownType, issuer.pathname);
url = new URL(wellKnownPath, (_b = opts === null || opts === void 0 ? void 0 : opts.metadataServerUrl) !== null && _b !== void 0 ? _b : issuer);
url.search = issuer.search;
}
let response = await tryMetadataDiscovery(url, protocolVersion, fetchFn);
// If path-aware discovery fails with 404 and we're not already at root, try fallback to root discovery
if (!(opts === null || opts === void 0 ? void 0 : opts.metadataUrl) && shouldAttemptFallback(response, issuer.pathname)) {
const rootUrl = new URL(`/.well-known/${wellKnownType}`, issuer);
response = await tryMetadataDiscovery(rootUrl, protocolVersion, fetchFn);
}
return response;
}
/**
* Looks up RFC 8414 OAuth 2.0 Authorization Server Metadata.
*
* If the server returns a 404 for the well-known endpoint, this function will
* return `undefined`. Any other errors will be thrown as exceptions.
*
* @deprecated This function is deprecated in favor of `discoverAuthorizationServerMetadata`.
*/
async function discoverOAuthMetadata(issuer, { authorizationServerUrl, protocolVersion, } = {}, fetchFn = fetch) {
if (typeof issuer === 'string') {
issuer = new URL(issuer);
}
if (!authorizationServerUrl) {
authorizationServerUrl = issuer;
}
if (typeof authorizationServerUrl === 'string') {
authorizationServerUrl = new URL(authorizationServerUrl);
}
protocolVersion !== null && protocolVersion !== void 0 ? protocolVersion : (protocolVersion = types_js_1.LATEST_PROTOCOL_VERSION);
const response = await discoverMetadataWithFallback(authorizationServerUrl, 'oauth-authorization-server', fetchFn, {
protocolVersion,
metadataServerUrl: authorizationServerUrl,
});
if (!response || response.status === 404) {
return undefined;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status} trying to load well-known OAuth metadata`);
}
return auth_js_2.OAuthMetadataSchema.parse(await response.json());
}
/**
* Builds a list of discovery URLs to try for authorization server metadata.
* URLs are returned in priority order:
* 1. OAuth metadata at the given URL
* 2. OAuth metadata at root (if URL has path)
* 3. OIDC metadata endpoints
*/
function buildDiscoveryUrls(authorizationServerUrl) {
const url = typeof authorizationServerUrl === 'string' ? new URL(authorizationServerUrl) : authorizationServerUrl;
const hasPath = url.pathname !== '/';
const urlsToTry = [];
if (!hasPath) {
// Root path: https://example.com/.well-known/oauth-authorization-server
urlsToTry.push({
url: new URL('/.well-known/oauth-authorization-server', url.origin),
type: 'oauth'
});
// OIDC: https://example.com/.well-known/openid-configuration
urlsToTry.push({
url: new URL(`/.well-known/openid-configuration`, url.origin),
type: 'oidc'
});
return urlsToTry;
}
// Strip trailing slash from pathname to avoid double slashes
let pathname = url.pathname;
if (pathname.endsWith('/')) {
pathname = pathname.slice(0, -1);
}
// 1. OAuth metadata at the given URL
// Insert well-known before the path: https://example.com/.well-known/oauth-authorization-server/tenant1
urlsToTry.push({
url: new URL(`/.well-known/oauth-authorization-server${pathname}`, url.origin),
type: 'oauth'
});
// Root path: https://example.com/.well-known/oauth-authorization-server
urlsToTry.push({
url: new URL('/.well-known/oauth-authorization-server', url.origin),
type: 'oauth'
});
// 3. OIDC metadata endpoints
// RFC 8414 style: Insert /.well-known/openid-configuration before the path
urlsToTry.push({
url: new URL(`/.well-known/openid-configuration${pathname}`, url.origin),
type: 'oidc'
});
// OIDC Discovery 1.0 style: Append /.well-known/openid-configuration after the path
urlsToTry.push({
url: new URL(`${pathname}/.well-known/openid-configuration`, url.origin),
type: 'oidc'
});
return urlsToTry;
}
/**
* Discovers authorization server metadata with support for RFC 8414 OAuth 2.0 Authorization Server Metadata
* and OpenID Connect Discovery 1.0 specifications.
*
* This function implements a fallback strategy for authorization server discovery:
* 1. Attempts RFC 8414 OAuth metadata discovery first
* 2. If OAuth discovery fails, falls back to OpenID Connect Discovery
*
* @param authorizationServerUrl - The authorization server URL obtained from the MCP Server's
* protected resource metadata, or the MCP server's URL if the
* metadata was not found.
* @param options - Configuration options
* @param options.fetchFn - Optional fetch function for making HTTP requests, defaults to global fetch
* @param options.protocolVersion - MCP protocol version to use, defaults to LATEST_PROTOCOL_VERSION
* @returns Promise resolving to authorization server metadata, or undefined if discovery fails
*/
async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn = fetch, protocolVersion = types_js_1.LATEST_PROTOCOL_VERSION, } = {}) {
var _a;
const headers = { 'MCP-Protocol-Version': protocolVersion };
// Get the list of URLs to try
const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
// Try each URL in order
for (const { url: endpointUrl, type } of urlsToTry) {
const response = await fetchWithCorsRetry(endpointUrl, headers, fetchFn);
if (!response) {
/**
* CORS error occurred - don't throw as the endpoint may not allow CORS,
* continue trying other possible endpoints
*/
continue;
}
if (!response.ok) {
// Continue looking for any 4xx response code.
if (response.status >= 400 && response.status < 500) {
continue; // Try next URL
}
throw new Error(`HTTP ${response.status} trying to load ${type === 'oauth' ? 'OAuth' : 'OpenID provider'} metadata from ${endpointUrl}`);
}
// Parse and validate based on type
if (type === 'oauth') {
return auth_js_2.OAuthMetadataSchema.parse(await response.json());
}
else {
const metadata = auth_js_1.OpenIdProviderDiscoveryMetadataSchema.parse(await response.json());
// MCP spec requires OIDC providers to support S256 PKCE
if (!((_a = metadata.code_challenge_methods_supported) === null || _a === void 0 ? void 0 : _a.includes('S256'))) {
throw new Error(`Incompatible OIDC provider at ${endpointUrl}: does not support S256 code challenge method required by MCP specification`);
}
return metadata;
}
}
return undefined;
}
/**
* Begins the authorization flow with the given server, by generating a PKCE challenge and constructing the authorization URL.
*/
async function startAuthorization(authorizationServerUrl, { metadata, clientInformation, redirectUrl, scope, state, resource, }) {
const responseType = "code";
const codeChallengeMethod = "S256";
let authorizationUrl;
if (metadata) {
authorizationUrl = new URL(metadata.authorization_endpoint);
if (!metadata.response_types_supported.includes(responseType)) {
throw new Error(`Incompatible auth server: does not support response type ${responseType}`);
}
if (!metadata.code_challenge_methods_supported ||
!metadata.code_challenge_methods_supported.includes(codeChallengeMethod)) {
throw new Error(`Incompatible auth server: does not support code challenge method ${codeChallengeMethod}`);
}
}
else {
authorizationUrl = new URL("/authorize", authorizationServerUrl);
}
// Generate PKCE challenge
const challenge = await (0, pkce_challenge_1.default)();
const codeVerifier = challenge.code_verifier;
const codeChallenge = challenge.code_challenge;
authorizationUrl.searchParams.set("response_type", responseType);
authorizationUrl.searchParams.set("client_id", clientInformation.client_id);
authorizationUrl.searchParams.set("code_challenge", codeChallenge);
authorizationUrl.searchParams.set("code_challenge_method", codeChallengeMethod);
authorizationUrl.searchParams.set("redirect_uri", String(redirectUrl));
if (state) {
authorizationUrl.searchParams.set("state", state);
}
if (scope) {
authorizationUrl.searchParams.set("scope", scope);
}
if (scope === null || scope === void 0 ? void 0 : scope.includes("offline_access")) {
// if the request includes the OIDC-only "offline_access" scope,
// we need to set the prompt to "consent" to ensure the user is prompted to grant offline access
// https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
authorizationUrl.searchParams.append("prompt", "consent");
}
if (resource) {
authorizationUrl.searchParams.set("resource", resource.href);
}
return { authorizationUrl, codeVerifier };
}
/**
* Exchanges an authorization code for an access token with the given server.
*
* Supports multiple client authentication methods as specified in OAuth 2.1:
* - Automatically selects the best authentication method based on server support
* - Falls back to appropriate defaults when server metadata is unavailable
*
* @param authorizationServerUrl - The authorization server's base URL
* @param options - Configuration object containing client info, auth code, etc.
* @returns Promise resolving to OAuth tokens
* @throws {Error} When token exchange fails or authentication is invalid
*/
async function exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn, }) {
var _a;
const grantType = "authorization_code";
const tokenUrl = (metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint)
? new URL(metadata.token_endpoint)
: new URL("/token", authorizationServerUrl);
if ((metadata === null || metadata === void 0 ? void 0 : metadata.grant_types_supported) &&
!metadata.grant_types_supported.includes(grantType)) {
throw new Error(`Incompatible auth server: does not support grant type ${grantType}`);
}
// Exchange code for tokens
const headers = new Headers({
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
});
const params = new URLSearchParams({
grant_type: grantType,
code: authorizationCode,
code_verifier: codeVerifier,
redirect_uri: String(redirectUri),
});
if (addClientAuthentication) {
addClientAuthentication(headers, params, authorizationServerUrl, metadata);
}
else {
// Determine and apply client authentication method
const supportedMethods = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint_auth_methods_supported) !== null && _a !== void 0 ? _a : [];
const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
applyClientAuthentication(authMethod, clientInformation, headers, params);
}
if (resource) {
params.set("resource", resource.href);
}
const response = await (fetchFn !== null && fetchFn !== void 0 ? fetchFn : fetch)(tokenUrl, {
method: "POST",
headers,
body: params,
});
if (!response.ok) {
throw await parseErrorResponse(response);
}
return auth_js_2.OAuthTokensSchema.parse(await response.json());
}
/**
* Exchange a refresh token for an updated access token.
*
* Supports multiple client authentication methods as specified in OAuth 2.1:
* - Automatically selects the best authentication method based on server support
* - Preserves the original refresh token if a new one is not returned
*
* @param authorizationServerUrl - The authorization server's base URL
* @param options - Configuration object containing client info, refresh token, etc.
* @returns Promise resolving to OAuth tokens (preserves original refresh_token if not replaced)
* @throws {Error} When token refresh fails or authentication is invalid
*/
async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn, }) {
var _a;
const grantType = "refresh_token";
let tokenUrl;
if (metadata) {
tokenUrl = new URL(metadata.token_endpoint);
if (metadata.grant_types_supported &&
!metadata.grant_types_supported.includes(grantType)) {
throw new Error(`Incompatible auth server: does not support grant type ${grantType}`);
}
}
else {
tokenUrl = new URL("/token", authorizationServerUrl);
}
// Exchange refresh token
const headers = new Headers({
"Content-Type": "application/x-www-form-urlencoded",
});
const params = new URLSearchParams({
grant_type: grantType,
refresh_token: refreshToken,
});
if (addClientAuthentication) {
addClientAuthentication(headers, params, authorizationServerUrl, metadata);
}
else {
// Determine and apply client authentication method
const supportedMethods = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint_auth_methods_supported) !== null && _a !== void 0 ? _a : [];
const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
applyClientAuthentication(authMethod, clientInformation, headers, params);
}
if (resource) {
params.set("resource", resource.href);
}
const response = await (fetchFn !== null && fetchFn !== void 0 ? fetchFn : fetch)(tokenUrl, {
method: "POST",
headers,
body: params,
});
if (!response.ok) {
throw await parseErrorResponse(response);
}
return auth_js_2.OAuthTokensSchema.parse({ refresh_token: refreshToken, ...(await response.json()) });
}
/**
* Performs OAuth 2.0 Dynamic Client Registration according to RFC 7591.
*/
async function registerClient(authorizationServerUrl, { metadata, clientMetadata, fetchFn, }) {
let registrationUrl;
if (metadata) {
if (!metadata.registration_endpoint) {
throw new Error("Incompatible auth server: does not support dynamic client registration");
}
registrationUrl = new URL(metadata.registration_endpoint);
}
else {
registrationUrl = new URL("/register", authorizationServerUrl);
}
const response = await (fetchFn !== null && fetchFn !== void 0 ? fetchFn : fetch)(registrationUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(clientMetadata),
});
if (!response.ok) {
throw await parseErrorResponse(response);
}
return auth_js_2.OAuthClientInformationFullSchema.parse(await response.json());
}
//# sourceMappingURL=auth.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 04ecc2548d05c4c5eb23c3d566fa2a0d
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 3d8abdd1c62ec443c9f9a7d23311de44
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js
uploadId: 920982
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 55e0defd67a194cbc870c5280a213d2a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/index.d.ts.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 8b1667f714623491594e35c5b31d04eb
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/index.d.ts
uploadId: 920982
@@ -0,0 +1,295 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Client = void 0;
const protocol_js_1 = require("../shared/protocol.js");
const types_js_1 = require("../types.js");
const ajv_1 = __importDefault(require("ajv"));
/**
* An MCP client on top of a pluggable transport.
*
* The client will automatically begin the initialization flow with the server when connect() is called.
*
* To use with custom types, extend the base Request/Notification/Result types and pass them as type parameters:
*
* ```typescript
* // Custom schemas
* const CustomRequestSchema = RequestSchema.extend({...})
* const CustomNotificationSchema = NotificationSchema.extend({...})
* const CustomResultSchema = ResultSchema.extend({...})
*
* // Type aliases
* type CustomRequest = z.infer<typeof CustomRequestSchema>
* type CustomNotification = z.infer<typeof CustomNotificationSchema>
* type CustomResult = z.infer<typeof CustomResultSchema>
*
* // Create typed client
* const client = new Client<CustomRequest, CustomNotification, CustomResult>({
* name: "CustomClient",
* version: "1.0.0"
* })
* ```
*/
class Client extends protocol_js_1.Protocol {
/**
* Initializes this client with the given name and version information.
*/
constructor(_clientInfo, options) {
var _a;
super(options);
this._clientInfo = _clientInfo;
this._cachedToolOutputValidators = new Map();
this._capabilities = (_a = options === null || options === void 0 ? void 0 : options.capabilities) !== null && _a !== void 0 ? _a : {};
this._ajv = new ajv_1.default();
}
/**
* Registers new capabilities. This can only be called before connecting to a transport.
*
* The new capabilities will be merged with any existing capabilities previously given (e.g., at initialization).
*/
registerCapabilities(capabilities) {
if (this.transport) {
throw new Error("Cannot register capabilities after connecting to transport");
}
this._capabilities = (0, protocol_js_1.mergeCapabilities)(this._capabilities, capabilities);
}
assertCapability(capability, method) {
var _a;
if (!((_a = this._serverCapabilities) === null || _a === void 0 ? void 0 : _a[capability])) {
throw new Error(`Server does not support ${capability} (required for ${method})`);
}
}
async connect(transport, options) {
await super.connect(transport);
// When transport sessionId is already set this means we are trying to reconnect.
// In this case we don't need to initialize again.
if (transport.sessionId !== undefined) {
return;
}
try {
const result = await this.request({
method: "initialize",
params: {
protocolVersion: types_js_1.LATEST_PROTOCOL_VERSION,
capabilities: this._capabilities,
clientInfo: this._clientInfo,
},
}, types_js_1.InitializeResultSchema, options);
if (result === undefined) {
throw new Error(`Server sent invalid initialize result: ${result}`);
}
if (!types_js_1.SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) {
throw new Error(`Server's protocol version is not supported: ${result.protocolVersion}`);
}
this._serverCapabilities = result.capabilities;
this._serverVersion = result.serverInfo;
// HTTP transports must set the protocol version in each header after initialization.
if (transport.setProtocolVersion) {
transport.setProtocolVersion(result.protocolVersion);
}
this._instructions = result.instructions;
await this.notification({
method: "notifications/initialized",
});
}
catch (error) {
// Disconnect if initialization fails.
void this.close();
throw error;
}
}
/**
* After initialization has completed, this will be populated with the server's reported capabilities.
*/
getServerCapabilities() {
return this._serverCapabilities;
}
/**
* After initialization has completed, this will be populated with information about the server's name and version.
*/
getServerVersion() {
return this._serverVersion;
}
/**
* After initialization has completed, this may be populated with information about the server's instructions.
*/
getInstructions() {
return this._instructions;
}
assertCapabilityForMethod(method) {
var _a, _b, _c, _d, _e;
switch (method) {
case "logging/setLevel":
if (!((_a = this._serverCapabilities) === null || _a === void 0 ? void 0 : _a.logging)) {
throw new Error(`Server does not support logging (required for ${method})`);
}
break;
case "prompts/get":
case "prompts/list":
if (!((_b = this._serverCapabilities) === null || _b === void 0 ? void 0 : _b.prompts)) {
throw new Error(`Server does not support prompts (required for ${method})`);
}
break;
case "resources/list":
case "resources/templates/list":
case "resources/read":
case "resources/subscribe":
case "resources/unsubscribe":
if (!((_c = this._serverCapabilities) === null || _c === void 0 ? void 0 : _c.resources)) {
throw new Error(`Server does not support resources (required for ${method})`);
}
if (method === "resources/subscribe" &&
!this._serverCapabilities.resources.subscribe) {
throw new Error(`Server does not support resource subscriptions (required for ${method})`);
}
break;
case "tools/call":
case "tools/list":
if (!((_d = this._serverCapabilities) === null || _d === void 0 ? void 0 : _d.tools)) {
throw new Error(`Server does not support tools (required for ${method})`);
}
break;
case "completion/complete":
if (!((_e = this._serverCapabilities) === null || _e === void 0 ? void 0 : _e.completions)) {
throw new Error(`Server does not support completions (required for ${method})`);
}
break;
case "initialize":
// No specific capability required for initialize
break;
case "ping":
// No specific capability required for ping
break;
}
}
assertNotificationCapability(method) {
var _a;
switch (method) {
case "notifications/roots/list_changed":
if (!((_a = this._capabilities.roots) === null || _a === void 0 ? void 0 : _a.listChanged)) {
throw new Error(`Client does not support roots list changed notifications (required for ${method})`);
}
break;
case "notifications/initialized":
// No specific capability required for initialized
break;
case "notifications/cancelled":
// Cancellation notifications are always allowed
break;
case "notifications/progress":
// Progress notifications are always allowed
break;
}
}
assertRequestHandlerCapability(method) {
switch (method) {
case "sampling/createMessage":
if (!this._capabilities.sampling) {
throw new Error(`Client does not support sampling capability (required for ${method})`);
}
break;
case "elicitation/create":
if (!this._capabilities.elicitation) {
throw new Error(`Client does not support elicitation capability (required for ${method})`);
}
break;
case "roots/list":
if (!this._capabilities.roots) {
throw new Error(`Client does not support roots capability (required for ${method})`);
}
break;
case "ping":
// No specific capability required for ping
break;
}
}
async ping(options) {
return this.request({ method: "ping" }, types_js_1.EmptyResultSchema, options);
}
async complete(params, options) {
return this.request({ method: "completion/complete", params }, types_js_1.CompleteResultSchema, options);
}
async setLoggingLevel(level, options) {
return this.request({ method: "logging/setLevel", params: { level } }, types_js_1.EmptyResultSchema, options);
}
async getPrompt(params, options) {
return this.request({ method: "prompts/get", params }, types_js_1.GetPromptResultSchema, options);
}
async listPrompts(params, options) {
return this.request({ method: "prompts/list", params }, types_js_1.ListPromptsResultSchema, options);
}
async listResources(params, options) {
return this.request({ method: "resources/list", params }, types_js_1.ListResourcesResultSchema, options);
}
async listResourceTemplates(params, options) {
return this.request({ method: "resources/templates/list", params }, types_js_1.ListResourceTemplatesResultSchema, options);
}
async readResource(params, options) {
return this.request({ method: "resources/read", params }, types_js_1.ReadResourceResultSchema, options);
}
async subscribeResource(params, options) {
return this.request({ method: "resources/subscribe", params }, types_js_1.EmptyResultSchema, options);
}
async unsubscribeResource(params, options) {
return this.request({ method: "resources/unsubscribe", params }, types_js_1.EmptyResultSchema, options);
}
async callTool(params, resultSchema = types_js_1.CallToolResultSchema, options) {
const result = await this.request({ method: "tools/call", params }, resultSchema, options);
// Check if the tool has an outputSchema
const validator = this.getToolOutputValidator(params.name);
if (validator) {
// If tool has outputSchema, it MUST return structuredContent (unless it's an error)
if (!result.structuredContent && !result.isError) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, `Tool ${params.name} has an output schema but did not return structured content`);
}
// Only validate structured content if present (not when there's an error)
if (result.structuredContent) {
try {
// Validate the structured content (which is already an object) against the schema
const isValid = validator(result.structuredContent);
if (!isValid) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Structured content does not match the tool's output schema: ${this._ajv.errorsText(validator.errors)}`);
}
}
catch (error) {
if (error instanceof types_js_1.McpError) {
throw error;
}
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Failed to validate structured content: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
return result;
}
cacheToolOutputSchemas(tools) {
this._cachedToolOutputValidators.clear();
for (const tool of tools) {
// If the tool has an outputSchema, create and cache the Ajv validator
if (tool.outputSchema) {
try {
const validator = this._ajv.compile(tool.outputSchema);
this._cachedToolOutputValidators.set(tool.name, validator);
}
catch (_a) {
// Ignore schema compilation errors
}
}
}
}
getToolOutputValidator(toolName) {
return this._cachedToolOutputValidators.get(toolName);
}
async listTools(params, options) {
const result = await this.request({ method: "tools/list", params }, types_js_1.ListToolsResultSchema, options);
// Cache the tools and their output schemas for future validation
this.cacheToolOutputSchemas(result.tools);
return result;
}
async sendRootsListChanged() {
return this.notification({ method: "notifications/roots/list_changed" });
}
}
exports.Client = Client;
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: f568573b83f7746ca83b151508232e6a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/index.js.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 505ced64c49474acd896afc5c97da99c
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/index.js
uploadId: 920982
@@ -0,0 +1,169 @@
import { OAuthClientProvider } from "./auth.js";
import { FetchLike } from "../shared/transport.js";
/**
* Middleware function that wraps and enhances fetch functionality.
* Takes a fetch handler and returns an enhanced fetch handler.
*/
export type Middleware = (next: FetchLike) => FetchLike;
/**
* Creates a fetch wrapper that handles OAuth authentication automatically.
*
* This wrapper will:
* - Add Authorization headers with access tokens
* - Handle 401 responses by attempting re-authentication
* - Retry the original request after successful auth
* - Handle OAuth errors appropriately (InvalidClientError, etc.)
*
* The baseUrl parameter is optional and defaults to using the domain from the request URL.
* However, you should explicitly provide baseUrl when:
* - Making requests to multiple subdomains (e.g., api.example.com, cdn.example.com)
* - Using API paths that differ from OAuth discovery paths (e.g., requesting /api/v1/data but OAuth is at /)
* - The OAuth server is on a different domain than your API requests
* - You want to ensure consistent OAuth behavior regardless of request URLs
*
* For MCP transports, set baseUrl to the same URL you pass to the transport constructor.
*
* Note: This wrapper is designed for general-purpose fetch operations.
* MCP transports (SSE and StreamableHTTP) already have built-in OAuth handling
* and should not need this wrapper.
*
* @param provider - OAuth client provider for authentication
* @param baseUrl - Base URL for OAuth server discovery (defaults to request URL domain)
* @returns A fetch middleware function
*/
export declare const withOAuth: (provider: OAuthClientProvider, baseUrl?: string | URL) => Middleware;
/**
* Logger function type for HTTP requests
*/
export type RequestLogger = (input: {
method: string;
url: string | URL;
status: number;
statusText: string;
duration: number;
requestHeaders?: Headers;
responseHeaders?: Headers;
error?: Error;
}) => void;
/**
* Configuration options for the logging middleware
*/
export type LoggingOptions = {
/**
* Custom logger function, defaults to console logging
*/
logger?: RequestLogger;
/**
* Whether to include request headers in logs
* @default false
*/
includeRequestHeaders?: boolean;
/**
* Whether to include response headers in logs
* @default false
*/
includeResponseHeaders?: boolean;
/**
* Status level filter - only log requests with status >= this value
* Set to 0 to log all requests, 400 to log only errors
* @default 0
*/
statusLevel?: number;
};
/**
* Creates a fetch middleware that logs HTTP requests and responses.
*
* When called without arguments `withLogging()`, it uses the default logger that:
* - Logs successful requests (2xx) to `console.log`
* - Logs error responses (4xx/5xx) and network errors to `console.error`
* - Logs all requests regardless of status (statusLevel: 0)
* - Does not include request or response headers in logs
* - Measures and displays request duration in milliseconds
*
* Important: the default logger uses both `console.log` and `console.error` so it should not be used with
* `stdio` transports and applications.
*
* @param options - Logging configuration options
* @returns A fetch middleware function
*/
export declare const withLogging: (options?: LoggingOptions) => Middleware;
/**
* Composes multiple fetch middleware functions into a single middleware pipeline.
* Middleware are applied in the order they appear, creating a chain of handlers.
*
* @example
* ```typescript
* // Create a middleware pipeline that handles both OAuth and logging
* const enhancedFetch = applyMiddlewares(
* withOAuth(oauthProvider, 'https://api.example.com'),
* withLogging({ statusLevel: 400 })
* )(fetch);
*
* // Use the enhanced fetch - it will handle auth and log errors
* const response = await enhancedFetch('https://api.example.com/data');
* ```
*
* @param middleware - Array of fetch middleware to compose into a pipeline
* @returns A single composed middleware function
*/
export declare const applyMiddlewares: (...middleware: Middleware[]) => Middleware;
/**
* Helper function to create custom fetch middleware with cleaner syntax.
* Provides the next handler and request details as separate parameters for easier access.
*
* @example
* ```typescript
* // Create custom authentication middleware
* const customAuthMiddleware = createMiddleware(async (next, input, init) => {
* const headers = new Headers(init?.headers);
* headers.set('X-Custom-Auth', 'my-token');
*
* const response = await next(input, { ...init, headers });
*
* if (response.status === 401) {
* console.log('Authentication failed');
* }
*
* return response;
* });
*
* // Create conditional middleware
* const conditionalMiddleware = createMiddleware(async (next, input, init) => {
* const url = typeof input === 'string' ? input : input.toString();
*
* // Only add headers for API routes
* if (url.includes('/api/')) {
* const headers = new Headers(init?.headers);
* headers.set('X-API-Version', 'v2');
* return next(input, { ...init, headers });
* }
*
* // Pass through for non-API routes
* return next(input, init);
* });
*
* // Create caching middleware
* const cacheMiddleware = createMiddleware(async (next, input, init) => {
* const cacheKey = typeof input === 'string' ? input : input.toString();
*
* // Check cache first
* const cached = await getFromCache(cacheKey);
* if (cached) {
* return new Response(cached, { status: 200 });
* }
*
* // Make request and cache result
* const response = await next(input, init);
* if (response.ok) {
* await saveToCache(cacheKey, await response.clone().text());
* }
*
* return response;
* });
* ```
*
* @param handler - Function that receives the next handler and request parameters
* @returns A fetch middleware function
*/
export declare const createMiddleware: (handler: (next: FetchLike, input: string | URL, init?: RequestInit) => Promise<Response>) => Middleware;
//# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/client/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,mBAAmB,EAEpB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,SAAS,KAAK,SAAS,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,SAAS,aACT,mBAAmB,YAAY,MAAM,GAAG,GAAG,KAAG,UAiExD,CAAC;AAEJ;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,KAAK,IAAI,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;OAEG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;IAEvB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAEhC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,WAAW,aAAa,cAAc,KAAQ,UA+F1D,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,kBACZ,UAAU,EAAE,KAC1B,UAIF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,eAAO,MAAM,gBAAgB,YAClB,CACP,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,MAAM,GAAG,GAAG,EACnB,IAAI,CAAC,EAAE,WAAW,KACf,OAAO,CAAC,QAAQ,CAAC,KACrB,UAEF,CAAC"}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 6db7e2a7eeaaa40cda42c3b33d1565ba
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/middleware.d.ts.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: d6f72c169a75b44fe9de9a039b463ba6
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/middleware.d.ts
uploadId: 920982
@@ -0,0 +1,256 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createMiddleware = exports.applyMiddlewares = exports.withLogging = exports.withOAuth = void 0;
const auth_js_1 = require("./auth.js");
/**
* Creates a fetch wrapper that handles OAuth authentication automatically.
*
* This wrapper will:
* - Add Authorization headers with access tokens
* - Handle 401 responses by attempting re-authentication
* - Retry the original request after successful auth
* - Handle OAuth errors appropriately (InvalidClientError, etc.)
*
* The baseUrl parameter is optional and defaults to using the domain from the request URL.
* However, you should explicitly provide baseUrl when:
* - Making requests to multiple subdomains (e.g., api.example.com, cdn.example.com)
* - Using API paths that differ from OAuth discovery paths (e.g., requesting /api/v1/data but OAuth is at /)
* - The OAuth server is on a different domain than your API requests
* - You want to ensure consistent OAuth behavior regardless of request URLs
*
* For MCP transports, set baseUrl to the same URL you pass to the transport constructor.
*
* Note: This wrapper is designed for general-purpose fetch operations.
* MCP transports (SSE and StreamableHTTP) already have built-in OAuth handling
* and should not need this wrapper.
*
* @param provider - OAuth client provider for authentication
* @param baseUrl - Base URL for OAuth server discovery (defaults to request URL domain)
* @returns A fetch middleware function
*/
const withOAuth = (provider, baseUrl) => (next) => {
return async (input, init) => {
const makeRequest = async () => {
const headers = new Headers(init === null || init === void 0 ? void 0 : init.headers);
// Add authorization header if tokens are available
const tokens = await provider.tokens();
if (tokens) {
headers.set("Authorization", `Bearer ${tokens.access_token}`);
}
return await next(input, { ...init, headers });
};
let response = await makeRequest();
// Handle 401 responses by attempting re-authentication
if (response.status === 401) {
try {
const resourceMetadataUrl = (0, auth_js_1.extractResourceMetadataUrl)(response);
// Use provided baseUrl or extract from request URL
const serverUrl = baseUrl ||
(typeof input === "string" ? new URL(input).origin : input.origin);
const result = await (0, auth_js_1.auth)(provider, {
serverUrl,
resourceMetadataUrl,
fetchFn: next,
});
if (result === "REDIRECT") {
throw new auth_js_1.UnauthorizedError("Authentication requires user authorization - redirect initiated");
}
if (result !== "AUTHORIZED") {
throw new auth_js_1.UnauthorizedError(`Authentication failed with result: ${result}`);
}
// Retry the request with fresh tokens
response = await makeRequest();
}
catch (error) {
if (error instanceof auth_js_1.UnauthorizedError) {
throw error;
}
throw new auth_js_1.UnauthorizedError(`Failed to re-authenticate: ${error instanceof Error ? error.message : String(error)}`);
}
}
// If we still have a 401 after re-auth attempt, throw an error
if (response.status === 401) {
const url = typeof input === "string" ? input : input.toString();
throw new auth_js_1.UnauthorizedError(`Authentication failed for ${url}`);
}
return response;
};
};
exports.withOAuth = withOAuth;
/**
* Creates a fetch middleware that logs HTTP requests and responses.
*
* When called without arguments `withLogging()`, it uses the default logger that:
* - Logs successful requests (2xx) to `console.log`
* - Logs error responses (4xx/5xx) and network errors to `console.error`
* - Logs all requests regardless of status (statusLevel: 0)
* - Does not include request or response headers in logs
* - Measures and displays request duration in milliseconds
*
* Important: the default logger uses both `console.log` and `console.error` so it should not be used with
* `stdio` transports and applications.
*
* @param options - Logging configuration options
* @returns A fetch middleware function
*/
const withLogging = (options = {}) => {
const { logger, includeRequestHeaders = false, includeResponseHeaders = false, statusLevel = 0, } = options;
const defaultLogger = (input) => {
const { method, url, status, statusText, duration, requestHeaders, responseHeaders, error, } = input;
let message = error
? `HTTP ${method} ${url} failed: ${error.message} (${duration}ms)`
: `HTTP ${method} ${url} ${status} ${statusText} (${duration}ms)`;
// Add headers to message if requested
if (includeRequestHeaders && requestHeaders) {
const reqHeaders = Array.from(requestHeaders.entries())
.map(([key, value]) => `${key}: ${value}`)
.join(", ");
message += `\n Request Headers: {${reqHeaders}}`;
}
if (includeResponseHeaders && responseHeaders) {
const resHeaders = Array.from(responseHeaders.entries())
.map(([key, value]) => `${key}: ${value}`)
.join(", ");
message += `\n Response Headers: {${resHeaders}}`;
}
if (error || status >= 400) {
// eslint-disable-next-line no-console
console.error(message);
}
else {
// eslint-disable-next-line no-console
console.log(message);
}
};
const logFn = logger || defaultLogger;
return (next) => async (input, init) => {
const startTime = performance.now();
const method = (init === null || init === void 0 ? void 0 : init.method) || "GET";
const url = typeof input === "string" ? input : input.toString();
const requestHeaders = includeRequestHeaders
? new Headers(init === null || init === void 0 ? void 0 : init.headers)
: undefined;
try {
const response = await next(input, init);
const duration = performance.now() - startTime;
// Only log if status meets the log level threshold
if (response.status >= statusLevel) {
logFn({
method,
url,
status: response.status,
statusText: response.statusText,
duration,
requestHeaders,
responseHeaders: includeResponseHeaders
? response.headers
: undefined,
});
}
return response;
}
catch (error) {
const duration = performance.now() - startTime;
// Always log errors regardless of log level
logFn({
method,
url,
status: 0,
statusText: "Network Error",
duration,
requestHeaders,
error: error,
});
throw error;
}
};
};
exports.withLogging = withLogging;
/**
* Composes multiple fetch middleware functions into a single middleware pipeline.
* Middleware are applied in the order they appear, creating a chain of handlers.
*
* @example
* ```typescript
* // Create a middleware pipeline that handles both OAuth and logging
* const enhancedFetch = applyMiddlewares(
* withOAuth(oauthProvider, 'https://api.example.com'),
* withLogging({ statusLevel: 400 })
* )(fetch);
*
* // Use the enhanced fetch - it will handle auth and log errors
* const response = await enhancedFetch('https://api.example.com/data');
* ```
*
* @param middleware - Array of fetch middleware to compose into a pipeline
* @returns A single composed middleware function
*/
const applyMiddlewares = (...middleware) => {
return (next) => {
return middleware.reduce((handler, mw) => mw(handler), next);
};
};
exports.applyMiddlewares = applyMiddlewares;
/**
* Helper function to create custom fetch middleware with cleaner syntax.
* Provides the next handler and request details as separate parameters for easier access.
*
* @example
* ```typescript
* // Create custom authentication middleware
* const customAuthMiddleware = createMiddleware(async (next, input, init) => {
* const headers = new Headers(init?.headers);
* headers.set('X-Custom-Auth', 'my-token');
*
* const response = await next(input, { ...init, headers });
*
* if (response.status === 401) {
* console.log('Authentication failed');
* }
*
* return response;
* });
*
* // Create conditional middleware
* const conditionalMiddleware = createMiddleware(async (next, input, init) => {
* const url = typeof input === 'string' ? input : input.toString();
*
* // Only add headers for API routes
* if (url.includes('/api/')) {
* const headers = new Headers(init?.headers);
* headers.set('X-API-Version', 'v2');
* return next(input, { ...init, headers });
* }
*
* // Pass through for non-API routes
* return next(input, init);
* });
*
* // Create caching middleware
* const cacheMiddleware = createMiddleware(async (next, input, init) => {
* const cacheKey = typeof input === 'string' ? input : input.toString();
*
* // Check cache first
* const cached = await getFromCache(cacheKey);
* if (cached) {
* return new Response(cached, { status: 200 });
* }
*
* // Make request and cache result
* const response = await next(input, init);
* if (response.ok) {
* await saveToCache(cacheKey, await response.clone().text());
* }
*
* return response;
* });
* ```
*
* @param handler - Function that receives the next handler and request parameters
* @returns A fetch middleware function
*/
const createMiddleware = (handler) => {
return (next) => (input, init) => handler(next, input, init);
};
exports.createMiddleware = createMiddleware;
//# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../src/client/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 4ae0929e2fe734e17878ece9ea2fd185
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/middleware.js.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: ca22b807cde2942d3ad85a1aeed4e4c1
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/middleware.js
uploadId: 920982
@@ -0,0 +1,78 @@
import { type ErrorEvent, type EventSourceInit } from "eventsource";
import { Transport, FetchLike } from "../shared/transport.js";
import { JSONRPCMessage } from "../types.js";
import { OAuthClientProvider } from "./auth.js";
export declare class SseError extends Error {
readonly code: number | undefined;
readonly event: ErrorEvent;
constructor(code: number | undefined, message: string | undefined, event: ErrorEvent);
}
/**
* Configuration options for the `SSEClientTransport`.
*/
export type SSEClientTransportOptions = {
/**
* An OAuth client provider to use for authentication.
*
* When an `authProvider` is specified and the SSE connection is started:
* 1. The connection is attempted with any existing access token from the `authProvider`.
* 2. If the access token has expired, the `authProvider` is used to refresh the token.
* 3. If token refresh fails or no access token exists, and auth is required, `OAuthClientProvider.redirectToAuthorization` is called, and an `UnauthorizedError` will be thrown from `connect`/`start`.
*
* After the user has finished authorizing via their user agent, and is redirected back to the MCP client application, call `SSEClientTransport.finishAuth` with the authorization code before retrying the connection.
*
* If an `authProvider` is not provided, and auth is required, an `UnauthorizedError` will be thrown.
*
* `UnauthorizedError` might also be thrown when sending any message over the SSE transport, indicating that the session has expired, and needs to be re-authed and reconnected.
*/
authProvider?: OAuthClientProvider;
/**
* Customizes the initial SSE request to the server (the request that begins the stream).
*
* NOTE: Setting this property will prevent an `Authorization` header from
* being automatically attached to the SSE request, if an `authProvider` is
* also given. This can be worked around by setting the `Authorization` header
* manually.
*/
eventSourceInit?: EventSourceInit;
/**
* Customizes recurring POST requests to the server.
*/
requestInit?: RequestInit;
/**
* Custom fetch implementation used for all network requests.
*/
fetch?: FetchLike;
};
/**
* Client transport for SSE: this will connect to a server using Server-Sent Events for receiving
* messages and make separate POST requests for sending messages.
*/
export declare class SSEClientTransport implements Transport {
private _eventSource?;
private _endpoint?;
private _abortController?;
private _url;
private _resourceMetadataUrl?;
private _eventSourceInit?;
private _requestInit?;
private _authProvider?;
private _fetch?;
private _protocolVersion?;
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage) => void;
constructor(url: URL, opts?: SSEClientTransportOptions);
private _authThenStart;
private _commonHeaders;
private _startOrAuth;
start(): Promise<void>;
/**
* Call this method after the user has finished authorizing via their user agent and is redirected back to the MCP client application. This will exchange the authorization code for an access token, enabling the next connection attempt to successfully auth.
*/
finishAuth(authorizationCode: string): Promise<void>;
close(): Promise<void>;
send(message: JSONRPCMessage): Promise<void>;
setProtocolVersion(version: string): void;
}
//# sourceMappingURL=sse.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../src/client/sse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAwB,MAAM,aAAa,CAAC;AACnE,OAAO,EAAgD,mBAAmB,EAAqB,MAAM,WAAW,CAAC;AAEjH,qBAAa,QAAS,SAAQ,KAAK;aAEf,IAAI,EAAE,MAAM,GAAG,SAAS;aAExB,KAAK,EAAE,UAAU;gBAFjB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxC,OAAO,EAAE,MAAM,GAAG,SAAS,EACX,KAAK,EAAE,UAAU;CAIpC;AAED;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAEnC;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC;;OAEG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,SAAS;IAClD,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,SAAS,CAAC,CAAM;IACxB,OAAO,CAAC,gBAAgB,CAAC,CAAkB;IAC3C,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,oBAAoB,CAAC,CAAM;IACnC,OAAO,CAAC,gBAAgB,CAAC,CAAkB;IAC3C,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,aAAa,CAAC,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAC,CAAY;IAC3B,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAElC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;gBAG5C,GAAG,EAAE,GAAG,EACR,IAAI,CAAC,EAAE,yBAAyB;YAUpB,cAAc;YAoBd,cAAc;IAiB5B,OAAO,CAAC,YAAY;IA6Ed,KAAK;IAUX;;OAEG;IACG,UAAU,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IA0ClD,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAG1C"}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 7dbd21648f0764e3d83da5fca4c4de1d
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.d.ts.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: b64bff088d08d498999c5f12f54ddc21
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.d.ts
uploadId: 920982
@@ -0,0 +1,189 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SSEClientTransport = exports.SseError = void 0;
const eventsource_1 = require("eventsource");
const types_js_1 = require("../types.js");
const auth_js_1 = require("./auth.js");
class SseError extends Error {
constructor(code, message, event) {
super(`SSE error: ${message}`);
this.code = code;
this.event = event;
}
}
exports.SseError = SseError;
/**
* Client transport for SSE: this will connect to a server using Server-Sent Events for receiving
* messages and make separate POST requests for sending messages.
*/
class SSEClientTransport {
constructor(url, opts) {
this._url = url;
this._resourceMetadataUrl = undefined;
this._eventSourceInit = opts === null || opts === void 0 ? void 0 : opts.eventSourceInit;
this._requestInit = opts === null || opts === void 0 ? void 0 : opts.requestInit;
this._authProvider = opts === null || opts === void 0 ? void 0 : opts.authProvider;
this._fetch = opts === null || opts === void 0 ? void 0 : opts.fetch;
}
async _authThenStart() {
var _a;
if (!this._authProvider) {
throw new auth_js_1.UnauthorizedError("No auth provider");
}
let result;
try {
result = await (0, auth_js_1.auth)(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
}
catch (error) {
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
throw error;
}
if (result !== "AUTHORIZED") {
throw new auth_js_1.UnauthorizedError();
}
return await this._startOrAuth();
}
async _commonHeaders() {
var _a;
const headers = {};
if (this._authProvider) {
const tokens = await this._authProvider.tokens();
if (tokens) {
headers["Authorization"] = `Bearer ${tokens.access_token}`;
}
}
if (this._protocolVersion) {
headers["mcp-protocol-version"] = this._protocolVersion;
}
return new Headers({ ...headers, ...(_a = this._requestInit) === null || _a === void 0 ? void 0 : _a.headers });
}
_startOrAuth() {
var _a, _b, _c;
const fetchImpl = ((_c = (_b = (_a = this === null || this === void 0 ? void 0 : this._eventSourceInit) === null || _a === void 0 ? void 0 : _a.fetch) !== null && _b !== void 0 ? _b : this._fetch) !== null && _c !== void 0 ? _c : fetch);
return new Promise((resolve, reject) => {
this._eventSource = new eventsource_1.EventSource(this._url.href, {
...this._eventSourceInit,
fetch: async (url, init) => {
const headers = await this._commonHeaders();
headers.set("Accept", "text/event-stream");
const response = await fetchImpl(url, {
...init,
headers,
});
if (response.status === 401 && response.headers.has('www-authenticate')) {
this._resourceMetadataUrl = (0, auth_js_1.extractResourceMetadataUrl)(response);
}
return response;
},
});
this._abortController = new AbortController();
this._eventSource.onerror = (event) => {
var _a;
if (event.code === 401 && this._authProvider) {
this._authThenStart().then(resolve, reject);
return;
}
const error = new SseError(event.code, event.message, event);
reject(error);
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
};
this._eventSource.onopen = () => {
// The connection is open, but we need to wait for the endpoint to be received.
};
this._eventSource.addEventListener("endpoint", (event) => {
var _a;
const messageEvent = event;
try {
this._endpoint = new URL(messageEvent.data, this._url);
if (this._endpoint.origin !== this._url.origin) {
throw new Error(`Endpoint origin does not match connection origin: ${this._endpoint.origin}`);
}
}
catch (error) {
reject(error);
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
void this.close();
return;
}
resolve();
});
this._eventSource.onmessage = (event) => {
var _a, _b;
const messageEvent = event;
let message;
try {
message = types_js_1.JSONRPCMessageSchema.parse(JSON.parse(messageEvent.data));
}
catch (error) {
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
return;
}
(_b = this.onmessage) === null || _b === void 0 ? void 0 : _b.call(this, message);
};
});
}
async start() {
if (this._eventSource) {
throw new Error("SSEClientTransport already started! If using Client class, note that connect() calls start() automatically.");
}
return await this._startOrAuth();
}
/**
* Call this method after the user has finished authorizing via their user agent and is redirected back to the MCP client application. This will exchange the authorization code for an access token, enabling the next connection attempt to successfully auth.
*/
async finishAuth(authorizationCode) {
if (!this._authProvider) {
throw new auth_js_1.UnauthorizedError("No auth provider");
}
const result = await (0, auth_js_1.auth)(this._authProvider, { serverUrl: this._url, authorizationCode, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
if (result !== "AUTHORIZED") {
throw new auth_js_1.UnauthorizedError("Failed to authorize");
}
}
async close() {
var _a, _b, _c;
(_a = this._abortController) === null || _a === void 0 ? void 0 : _a.abort();
(_b = this._eventSource) === null || _b === void 0 ? void 0 : _b.close();
(_c = this.onclose) === null || _c === void 0 ? void 0 : _c.call(this);
}
async send(message) {
var _a, _b, _c;
if (!this._endpoint) {
throw new Error("Not connected");
}
try {
const headers = await this._commonHeaders();
headers.set("content-type", "application/json");
const init = {
...this._requestInit,
method: "POST",
headers,
body: JSON.stringify(message),
signal: (_a = this._abortController) === null || _a === void 0 ? void 0 : _a.signal,
};
const response = await ((_b = this._fetch) !== null && _b !== void 0 ? _b : fetch)(this._endpoint, init);
if (!response.ok) {
if (response.status === 401 && this._authProvider) {
this._resourceMetadataUrl = (0, auth_js_1.extractResourceMetadataUrl)(response);
const result = await (0, auth_js_1.auth)(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
if (result !== "AUTHORIZED") {
throw new auth_js_1.UnauthorizedError();
}
// Purposely _not_ awaited, so we don't call onerror twice
return this.send(message);
}
const text = await response.text().catch(() => null);
throw new Error(`Error POSTing to endpoint (HTTP ${response.status}): ${text}`);
}
}
catch (error) {
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error);
throw error;
}
}
setProtocolVersion(version) {
this._protocolVersion = version;
}
}
exports.SSEClientTransport = SSEClientTransport;
//# sourceMappingURL=sse.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: cc30458864ce04ea2bc6085f52c106a9
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.js.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 7424f5a6e720b4224861600070c170cc
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.js
uploadId: 920982
@@ -0,0 +1,78 @@
import { IOType } from "node:child_process";
import { Stream } from "node:stream";
import { Transport } from "../shared/transport.js";
import { JSONRPCMessage } from "../types.js";
export type StdioServerParameters = {
/**
* The executable to run to start the server.
*/
command: string;
/**
* Command line arguments to pass to the executable.
*/
args?: string[];
/**
* The environment to use when spawning the process.
*
* If not specified, the result of getDefaultEnvironment() will be used.
*/
env?: Record<string, string>;
/**
* How to handle stderr of the child process. This matches the semantics of Node's `child_process.spawn`.
*
* The default is "inherit", meaning messages to stderr will be printed to the parent process's stderr.
*/
stderr?: IOType | Stream | number;
/**
* The working directory to use when spawning the process.
*
* If not specified, the current working directory will be inherited.
*/
cwd?: string;
};
/**
* Environment variables to inherit by default, if an environment is not explicitly given.
*/
export declare const DEFAULT_INHERITED_ENV_VARS: string[];
/**
* Returns a default environment object including only environment variables deemed safe to inherit.
*/
export declare function getDefaultEnvironment(): Record<string, string>;
/**
* Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout.
*
* This transport is only available in Node.js environments.
*/
export declare class StdioClientTransport implements Transport {
private _process?;
private _abortController;
private _readBuffer;
private _serverParams;
private _stderrStream;
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage) => void;
constructor(server: StdioServerParameters);
/**
* Starts the server process and prepares to communicate with it.
*/
start(): Promise<void>;
/**
* The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped".
*
* If stderr piping was requested, a PassThrough stream is returned _immediately_, allowing callers to
* attach listeners before the start method is invoked. This prevents loss of any early
* error output emitted by the child process.
*/
get stderr(): Stream | null;
/**
* The child process pid spawned by this transport.
*
* This is only available after the transport has been started.
*/
get pid(): number | null;
private processReadBuffer;
close(): Promise<void>;
send(message: JSONRPCMessage): Promise<void>;
}
//# sourceMappingURL=stdio.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../../src/client/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG1D,OAAO,EAAE,MAAM,EAAe,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,qBAAqB,GAAG;IAClC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAElC;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B,UAiBmB,CAAC;AAE3D;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkB9D;AAED;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,SAAS;IACpD,OAAO,CAAC,QAAQ,CAAC,CAAe;IAChC,OAAO,CAAC,gBAAgB,CAA0C;IAClE,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,aAAa,CAAwB;IAC7C,OAAO,CAAC,aAAa,CAA4B;IAEjD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;gBAElC,MAAM,EAAE,qBAAqB;IAOzC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgE5B;;;;;;OAMG;IACH,IAAI,MAAM,IAAI,MAAM,GAAG,IAAI,CAM1B;IAED;;;;OAIG;IACH,IAAI,GAAG,IAAI,MAAM,GAAG,IAAI,CAEvB;IAED,OAAO,CAAC,iBAAiB;IAenB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CAc7C"}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 43911f5fe1d3f4f2786d2b02e78dfe83
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/stdio.d.ts.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 1d435c87171c04214be2d5213ec39c1b
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/stdio.d.ts
uploadId: 920982
@@ -0,0 +1,184 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StdioClientTransport = exports.DEFAULT_INHERITED_ENV_VARS = void 0;
exports.getDefaultEnvironment = getDefaultEnvironment;
const cross_spawn_1 = __importDefault(require("cross-spawn"));
const node_process_1 = __importDefault(require("node:process"));
const node_stream_1 = require("node:stream");
const stdio_js_1 = require("../shared/stdio.js");
/**
* Environment variables to inherit by default, if an environment is not explicitly given.
*/
exports.DEFAULT_INHERITED_ENV_VARS = node_process_1.default.platform === "win32"
? [
"APPDATA",
"HOMEDRIVE",
"HOMEPATH",
"LOCALAPPDATA",
"PATH",
"PROCESSOR_ARCHITECTURE",
"SYSTEMDRIVE",
"SYSTEMROOT",
"TEMP",
"USERNAME",
"USERPROFILE",
"PROGRAMFILES",
]
: /* list inspired by the default env inheritance of sudo */
["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"];
/**
* Returns a default environment object including only environment variables deemed safe to inherit.
*/
function getDefaultEnvironment() {
const env = {};
for (const key of exports.DEFAULT_INHERITED_ENV_VARS) {
const value = node_process_1.default.env[key];
if (value === undefined) {
continue;
}
if (value.startsWith("()")) {
// Skip functions, which are a security risk.
continue;
}
env[key] = value;
}
return env;
}
/**
* Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout.
*
* This transport is only available in Node.js environments.
*/
class StdioClientTransport {
constructor(server) {
this._abortController = new AbortController();
this._readBuffer = new stdio_js_1.ReadBuffer();
this._stderrStream = null;
this._serverParams = server;
if (server.stderr === "pipe" || server.stderr === "overlapped") {
this._stderrStream = new node_stream_1.PassThrough();
}
}
/**
* Starts the server process and prepares to communicate with it.
*/
async start() {
if (this._process) {
throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
}
return new Promise((resolve, reject) => {
var _a, _b, _c, _d, _e;
this._process = (0, cross_spawn_1.default)(this._serverParams.command, (_a = this._serverParams.args) !== null && _a !== void 0 ? _a : [], {
// merge default env with server env because mcp server needs some env vars
env: {
...getDefaultEnvironment(),
...this._serverParams.env,
},
stdio: ["pipe", "pipe", (_b = this._serverParams.stderr) !== null && _b !== void 0 ? _b : "inherit"],
shell: false,
signal: this._abortController.signal,
windowsHide: node_process_1.default.platform === "win32" && isElectron(),
cwd: this._serverParams.cwd,
});
this._process.on("error", (error) => {
var _a, _b;
if (error.name === "AbortError") {
// Expected when close() is called.
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
return;
}
reject(error);
(_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
});
this._process.on("spawn", () => {
resolve();
});
this._process.on("close", (_code) => {
var _a;
this._process = undefined;
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
});
(_c = this._process.stdin) === null || _c === void 0 ? void 0 : _c.on("error", (error) => {
var _a;
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
});
(_d = this._process.stdout) === null || _d === void 0 ? void 0 : _d.on("data", (chunk) => {
this._readBuffer.append(chunk);
this.processReadBuffer();
});
(_e = this._process.stdout) === null || _e === void 0 ? void 0 : _e.on("error", (error) => {
var _a;
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
});
if (this._stderrStream && this._process.stderr) {
this._process.stderr.pipe(this._stderrStream);
}
});
}
/**
* The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped".
*
* If stderr piping was requested, a PassThrough stream is returned _immediately_, allowing callers to
* attach listeners before the start method is invoked. This prevents loss of any early
* error output emitted by the child process.
*/
get stderr() {
var _a, _b;
if (this._stderrStream) {
return this._stderrStream;
}
return (_b = (_a = this._process) === null || _a === void 0 ? void 0 : _a.stderr) !== null && _b !== void 0 ? _b : null;
}
/**
* The child process pid spawned by this transport.
*
* This is only available after the transport has been started.
*/
get pid() {
var _a, _b;
return (_b = (_a = this._process) === null || _a === void 0 ? void 0 : _a.pid) !== null && _b !== void 0 ? _b : null;
}
processReadBuffer() {
var _a, _b;
while (true) {
try {
const message = this._readBuffer.readMessage();
if (message === null) {
break;
}
(_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, message);
}
catch (error) {
(_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
}
}
}
async close() {
this._abortController.abort();
this._process = undefined;
this._readBuffer.clear();
}
send(message) {
return new Promise((resolve) => {
var _a;
if (!((_a = this._process) === null || _a === void 0 ? void 0 : _a.stdin)) {
throw new Error("Not connected");
}
const json = (0, stdio_js_1.serializeMessage)(message);
if (this._process.stdin.write(json)) {
resolve();
}
else {
this._process.stdin.once("drain", resolve);
}
});
}
}
exports.StdioClientTransport = StdioClientTransport;
function isElectron() {
return "type" in node_process_1.default;
}
//# sourceMappingURL=stdio.js.map
@@ -0,0 +1 @@
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../../src/client/stdio.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 8ed932173d9fa48278607d06cf955668
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/stdio.js.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: ce89f542de20349cba45828d3d0a3c8f
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/stdio.js
uploadId: 920982
@@ -0,0 +1,156 @@
import { Transport, FetchLike } from "../shared/transport.js";
import { JSONRPCMessage } from "../types.js";
import { OAuthClientProvider } from "./auth.js";
export declare class StreamableHTTPError extends Error {
readonly code: number | undefined;
constructor(code: number | undefined, message: string | undefined);
}
/**
* Options for starting or authenticating an SSE connection
*/
export interface StartSSEOptions {
/**
* The resumption token used to continue long-running requests that were interrupted.
*
* This allows clients to reconnect and continue from where they left off.
*/
resumptionToken?: string;
/**
* A callback that is invoked when the resumption token changes.
*
* This allows clients to persist the latest token for potential reconnection.
*/
onresumptiontoken?: (token: string) => void;
/**
* Override Message ID to associate with the replay message
* so that response can be associate with the new resumed request.
*/
replayMessageId?: string | number;
}
/**
* Configuration options for reconnection behavior of the StreamableHTTPClientTransport.
*/
export interface StreamableHTTPReconnectionOptions {
/**
* Maximum backoff time between reconnection attempts in milliseconds.
* Default is 30000 (30 seconds).
*/
maxReconnectionDelay: number;
/**
* Initial backoff time between reconnection attempts in milliseconds.
* Default is 1000 (1 second).
*/
initialReconnectionDelay: number;
/**
* The factor by which the reconnection delay increases after each attempt.
* Default is 1.5.
*/
reconnectionDelayGrowFactor: number;
/**
* Maximum number of reconnection attempts before giving up.
* Default is 2.
*/
maxRetries: number;
}
/**
* Configuration options for the `StreamableHTTPClientTransport`.
*/
export type StreamableHTTPClientTransportOptions = {
/**
* An OAuth client provider to use for authentication.
*
* When an `authProvider` is specified and the connection is started:
* 1. The connection is attempted with any existing access token from the `authProvider`.
* 2. If the access token has expired, the `authProvider` is used to refresh the token.
* 3. If token refresh fails or no access token exists, and auth is required, `OAuthClientProvider.redirectToAuthorization` is called, and an `UnauthorizedError` will be thrown from `connect`/`start`.
*
* After the user has finished authorizing via their user agent, and is redirected back to the MCP client application, call `StreamableHTTPClientTransport.finishAuth` with the authorization code before retrying the connection.
*
* If an `authProvider` is not provided, and auth is required, an `UnauthorizedError` will be thrown.
*
* `UnauthorizedError` might also be thrown when sending any message over the transport, indicating that the session has expired, and needs to be re-authed and reconnected.
*/
authProvider?: OAuthClientProvider;
/**
* Customizes HTTP requests to the server.
*/
requestInit?: RequestInit;
/**
* Custom fetch implementation used for all network requests.
*/
fetch?: FetchLike;
/**
* Options to configure the reconnection behavior.
*/
reconnectionOptions?: StreamableHTTPReconnectionOptions;
/**
* Session ID for the connection. This is used to identify the session on the server.
* When not provided and connecting to a server that supports session IDs, the server will generate a new session ID.
*/
sessionId?: string;
};
/**
* Client transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
* It will connect to a server using HTTP POST for sending messages and HTTP GET with Server-Sent Events
* for receiving messages.
*/
export declare class StreamableHTTPClientTransport implements Transport {
private _abortController?;
private _url;
private _resourceMetadataUrl?;
private _requestInit?;
private _authProvider?;
private _fetch?;
private _sessionId?;
private _reconnectionOptions;
private _protocolVersion?;
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage) => void;
constructor(url: URL, opts?: StreamableHTTPClientTransportOptions);
private _authThenStart;
private _commonHeaders;
private _startOrAuthSse;
/**
* Calculates the next reconnection delay using backoff algorithm
*
* @param attempt Current reconnection attempt count for the specific stream
* @returns Time to wait in milliseconds before next reconnection attempt
*/
private _getNextReconnectionDelay;
private _normalizeHeaders;
/**
* Schedule a reconnection attempt with exponential backoff
*
* @param lastEventId The ID of the last received event for resumability
* @param attemptCount Current reconnection attempt count for this specific stream
*/
private _scheduleReconnection;
private _handleSseStream;
start(): Promise<void>;
/**
* Call this method after the user has finished authorizing via their user agent and is redirected back to the MCP client application. This will exchange the authorization code for an access token, enabling the next connection attempt to successfully auth.
*/
finishAuth(authorizationCode: string): Promise<void>;
close(): Promise<void>;
send(message: JSONRPCMessage | JSONRPCMessage[], options?: {
resumptionToken?: string;
onresumptiontoken?: (token: string) => void;
}): Promise<void>;
get sessionId(): string | undefined;
/**
* Terminates the current session by sending a DELETE request to the server.
*
* Clients that no longer need a particular session
* (e.g., because the user is leaving the client application) SHOULD send an
* HTTP DELETE to the MCP endpoint with the Mcp-Session-Id header to explicitly
* terminate the session.
*
* The server MAY respond with HTTP 405 Method Not Allowed, indicating that
* the server does not allow clients to terminate sessions.
*/
terminateSession(): Promise<void>;
setProtocolVersion(version: string): void;
get protocolVersion(): string | undefined;
}
//# sourceMappingURL=streamableHttp.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"streamableHttp.d.ts","sourceRoot":"","sources":["../../../src/client/streamableHttp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 36ccdbbb89277450abebef039c270fe7
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.d.ts.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 53b371d9542db43ed969574c574bad16
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.d.ts
uploadId: 920982
@@ -0,0 +1,380 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StreamableHTTPClientTransport = exports.StreamableHTTPError = void 0;
const types_js_1 = require("../types.js");
const auth_js_1 = require("./auth.js");
const stream_1 = require("eventsource-parser/stream");
// Default reconnection options for StreamableHTTP connections
const DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS = {
initialReconnectionDelay: 1000,
maxReconnectionDelay: 30000,
reconnectionDelayGrowFactor: 1.5,
maxRetries: 2,
};
class StreamableHTTPError extends Error {
constructor(code, message) {
super(`Streamable HTTP error: ${message}`);
this.code = code;
}
}
exports.StreamableHTTPError = StreamableHTTPError;
/**
* Client transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
* It will connect to a server using HTTP POST for sending messages and HTTP GET with Server-Sent Events
* for receiving messages.
*/
class StreamableHTTPClientTransport {
constructor(url, opts) {
var _a;
this._url = url;
this._resourceMetadataUrl = undefined;
this._requestInit = opts === null || opts === void 0 ? void 0 : opts.requestInit;
this._authProvider = opts === null || opts === void 0 ? void 0 : opts.authProvider;
this._fetch = opts === null || opts === void 0 ? void 0 : opts.fetch;
this._sessionId = opts === null || opts === void 0 ? void 0 : opts.sessionId;
this._reconnectionOptions = (_a = opts === null || opts === void 0 ? void 0 : opts.reconnectionOptions) !== null && _a !== void 0 ? _a : DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS;
}
async _authThenStart() {
var _a;
if (!this._authProvider) {
throw new auth_js_1.UnauthorizedError("No auth provider");
}
let result;
try {
result = await (0, auth_js_1.auth)(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
}
catch (error) {
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
throw error;
}
if (result !== "AUTHORIZED") {
throw new auth_js_1.UnauthorizedError();
}
return await this._startOrAuthSse({ resumptionToken: undefined });
}
async _commonHeaders() {
var _a;
const headers = {};
if (this._authProvider) {
const tokens = await this._authProvider.tokens();
if (tokens) {
headers["Authorization"] = `Bearer ${tokens.access_token}`;
}
}
if (this._sessionId) {
headers["mcp-session-id"] = this._sessionId;
}
if (this._protocolVersion) {
headers["mcp-protocol-version"] = this._protocolVersion;
}
const extraHeaders = this._normalizeHeaders((_a = this._requestInit) === null || _a === void 0 ? void 0 : _a.headers);
return new Headers({
...headers,
...extraHeaders,
});
}
async _startOrAuthSse(options) {
var _a, _b, _c;
const { resumptionToken } = options;
try {
// Try to open an initial SSE stream with GET to listen for server messages
// This is optional according to the spec - server may not support it
const headers = await this._commonHeaders();
headers.set("Accept", "text/event-stream");
// Include Last-Event-ID header for resumable streams if provided
if (resumptionToken) {
headers.set("last-event-id", resumptionToken);
}
const response = await ((_a = this._fetch) !== null && _a !== void 0 ? _a : fetch)(this._url, {
method: "GET",
headers,
signal: (_b = this._abortController) === null || _b === void 0 ? void 0 : _b.signal,
});
if (!response.ok) {
if (response.status === 401 && this._authProvider) {
// Need to authenticate
return await this._authThenStart();
}
// 405 indicates that the server does not offer an SSE stream at GET endpoint
// This is an expected case that should not trigger an error
if (response.status === 405) {
return;
}
throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${response.statusText}`);
}
this._handleSseStream(response.body, options, true);
}
catch (error) {
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error);
throw error;
}
}
/**
* Calculates the next reconnection delay using backoff algorithm
*
* @param attempt Current reconnection attempt count for the specific stream
* @returns Time to wait in milliseconds before next reconnection attempt
*/
_getNextReconnectionDelay(attempt) {
// Access default values directly, ensuring they're never undefined
const initialDelay = this._reconnectionOptions.initialReconnectionDelay;
const growFactor = this._reconnectionOptions.reconnectionDelayGrowFactor;
const maxDelay = this._reconnectionOptions.maxReconnectionDelay;
// Cap at maximum delay
return Math.min(initialDelay * Math.pow(growFactor, attempt), maxDelay);
}
_normalizeHeaders(headers) {
if (!headers)
return {};
if (headers instanceof Headers) {
return Object.fromEntries(headers.entries());
}
if (Array.isArray(headers)) {
return Object.fromEntries(headers);
}
return { ...headers };
}
/**
* Schedule a reconnection attempt with exponential backoff
*
* @param lastEventId The ID of the last received event for resumability
* @param attemptCount Current reconnection attempt count for this specific stream
*/
_scheduleReconnection(options, attemptCount = 0) {
var _a;
// Use provided options or default options
const maxRetries = this._reconnectionOptions.maxRetries;
// Check if we've exceeded maximum retry attempts
if (maxRetries > 0 && attemptCount >= maxRetries) {
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));
return;
}
// Calculate next delay based on current attempt count
const delay = this._getNextReconnectionDelay(attemptCount);
// Schedule the reconnection
setTimeout(() => {
// Use the last event ID to resume where we left off
this._startOrAuthSse(options).catch(error => {
var _a;
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`Failed to reconnect SSE stream: ${error instanceof Error ? error.message : String(error)}`));
// Schedule another attempt if this one failed, incrementing the attempt counter
this._scheduleReconnection(options, attemptCount + 1);
});
}, delay);
}
_handleSseStream(stream, options, isReconnectable) {
if (!stream) {
return;
}
const { onresumptiontoken, replayMessageId } = options;
let lastEventId;
const processStream = async () => {
var _a, _b, _c, _d;
// this is the closest we can get to trying to catch network errors
// if something happens reader will throw
try {
// Create a pipeline: binary stream -> text decoder -> SSE parser
const reader = stream
.pipeThrough(new TextDecoderStream())
.pipeThrough(new stream_1.EventSourceParserStream())
.getReader();
while (true) {
const { value: event, done } = await reader.read();
if (done) {
break;
}
// Update last event ID if provided
if (event.id) {
lastEventId = event.id;
onresumptiontoken === null || onresumptiontoken === void 0 ? void 0 : onresumptiontoken(event.id);
}
if (!event.event || event.event === "message") {
try {
const message = types_js_1.JSONRPCMessageSchema.parse(JSON.parse(event.data));
if (replayMessageId !== undefined && (0, types_js_1.isJSONRPCResponse)(message)) {
message.id = replayMessageId;
}
(_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, message);
}
catch (error) {
(_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
}
}
}
}
catch (error) {
// Handle stream errors - likely a network disconnect
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, new Error(`SSE stream disconnected: ${error}`));
// Attempt to reconnect if the stream disconnects unexpectedly and we aren't closing
if (isReconnectable &&
this._abortController &&
!this._abortController.signal.aborted) {
// Use the exponential backoff reconnection strategy
try {
this._scheduleReconnection({
resumptionToken: lastEventId,
onresumptiontoken,
replayMessageId
}, 0);
}
catch (error) {
(_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, new Error(`Failed to reconnect: ${error instanceof Error ? error.message : String(error)}`));
}
}
}
};
processStream();
}
async start() {
if (this._abortController) {
throw new Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");
}
this._abortController = new AbortController();
}
/**
* Call this method after the user has finished authorizing via their user agent and is redirected back to the MCP client application. This will exchange the authorization code for an access token, enabling the next connection attempt to successfully auth.
*/
async finishAuth(authorizationCode) {
if (!this._authProvider) {
throw new auth_js_1.UnauthorizedError("No auth provider");
}
const result = await (0, auth_js_1.auth)(this._authProvider, { serverUrl: this._url, authorizationCode, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
if (result !== "AUTHORIZED") {
throw new auth_js_1.UnauthorizedError("Failed to authorize");
}
}
async close() {
var _a, _b;
// Abort any pending requests
(_a = this._abortController) === null || _a === void 0 ? void 0 : _a.abort();
(_b = this.onclose) === null || _b === void 0 ? void 0 : _b.call(this);
}
async send(message, options) {
var _a, _b, _c, _d;
try {
const { resumptionToken, onresumptiontoken } = options || {};
if (resumptionToken) {
// If we have at last event ID, we need to reconnect the SSE stream
this._startOrAuthSse({ resumptionToken, replayMessageId: (0, types_js_1.isJSONRPCRequest)(message) ? message.id : undefined }).catch(err => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err); });
return;
}
const headers = await this._commonHeaders();
headers.set("content-type", "application/json");
headers.set("accept", "application/json, text/event-stream");
const init = {
...this._requestInit,
method: "POST",
headers,
body: JSON.stringify(message),
signal: (_a = this._abortController) === null || _a === void 0 ? void 0 : _a.signal,
};
const response = await ((_b = this._fetch) !== null && _b !== void 0 ? _b : fetch)(this._url, init);
// Handle session ID received during initialization
const sessionId = response.headers.get("mcp-session-id");
if (sessionId) {
this._sessionId = sessionId;
}
if (!response.ok) {
if (response.status === 401 && this._authProvider) {
this._resourceMetadataUrl = (0, auth_js_1.extractResourceMetadataUrl)(response);
const result = await (0, auth_js_1.auth)(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
if (result !== "AUTHORIZED") {
throw new auth_js_1.UnauthorizedError();
}
// Purposely _not_ awaited, so we don't call onerror twice
return this.send(message);
}
const text = await response.text().catch(() => null);
throw new Error(`Error POSTing to endpoint (HTTP ${response.status}): ${text}`);
}
// If the response is 202 Accepted, there's no body to process
if (response.status === 202) {
// if the accepted notification is initialized, we start the SSE stream
// if it's supported by the server
if ((0, types_js_1.isInitializedNotification)(message)) {
// Start without a lastEventId since this is a fresh connection
this._startOrAuthSse({ resumptionToken: undefined }).catch(err => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err); });
}
return;
}
// Get original message(s) for detecting request IDs
const messages = Array.isArray(message) ? message : [message];
const hasRequests = messages.filter(msg => "method" in msg && "id" in msg && msg.id !== undefined).length > 0;
// Check the response type
const contentType = response.headers.get("content-type");
if (hasRequests) {
if (contentType === null || contentType === void 0 ? void 0 : contentType.includes("text/event-stream")) {
// Handle SSE stream responses for requests
// We use the same handler as standalone streams, which now supports
// reconnection with the last event ID
this._handleSseStream(response.body, { onresumptiontoken }, false);
}
else if (contentType === null || contentType === void 0 ? void 0 : contentType.includes("application/json")) {
// For non-streaming servers, we might get direct JSON responses
const data = await response.json();
const responseMessages = Array.isArray(data)
? data.map(msg => types_js_1.JSONRPCMessageSchema.parse(msg))
: [types_js_1.JSONRPCMessageSchema.parse(data)];
for (const msg of responseMessages) {
(_c = this.onmessage) === null || _c === void 0 ? void 0 : _c.call(this, msg);
}
}
else {
throw new StreamableHTTPError(-1, `Unexpected content type: ${contentType}`);
}
}
}
catch (error) {
(_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, error);
throw error;
}
}
get sessionId() {
return this._sessionId;
}
/**
* Terminates the current session by sending a DELETE request to the server.
*
* Clients that no longer need a particular session
* (e.g., because the user is leaving the client application) SHOULD send an
* HTTP DELETE to the MCP endpoint with the Mcp-Session-Id header to explicitly
* terminate the session.
*
* The server MAY respond with HTTP 405 Method Not Allowed, indicating that
* the server does not allow clients to terminate sessions.
*/
async terminateSession() {
var _a, _b, _c;
if (!this._sessionId) {
return; // No session to terminate
}
try {
const headers = await this._commonHeaders();
const init = {
...this._requestInit,
method: "DELETE",
headers,
signal: (_a = this._abortController) === null || _a === void 0 ? void 0 : _a.signal,
};
const response = await ((_b = this._fetch) !== null && _b !== void 0 ? _b : fetch)(this._url, init);
// We specifically handle 405 as a valid response according to the spec,
// meaning the server does not support explicit session termination
if (!response.ok && response.status !== 405) {
throw new StreamableHTTPError(response.status, `Failed to terminate session: ${response.statusText}`);
}
this._sessionId = undefined;
}
catch (error) {
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error);
throw error;
}
}
setProtocolVersion(version) {
this._protocolVersion = version;
}
get protocolVersion() {
return this._protocolVersion;
}
}
exports.StreamableHTTPClientTransport = StreamableHTTPClientTransport;
//# sourceMappingURL=streamableHttp.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 1862deb9a6d9542ef94402edbae5e54b
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.js.map
uploadId: 920982
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 96485ddb224f548d6afe3ec0301502c9
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.js
uploadId: 920982
@@ -0,0 +1,17 @@
import { Transport } from "../shared/transport.js";
import { JSONRPCMessage } from "../types.js";
/**
* Client transport for WebSocket: this will connect to a server over the WebSocket protocol.
*/
export declare class WebSocketClientTransport implements Transport {
private _socket?;
private _url;
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage) => void;
constructor(url: URL);
start(): Promise<void>;
close(): Promise<void>;
send(message: JSONRPCMessage): Promise<void>;
}
//# sourceMappingURL=websocket.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../../src/client/websocket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAwB,MAAM,aAAa,CAAC;AAInE;;GAEG;AACH,qBAAa,wBAAyB,YAAW,SAAS;IACxD,OAAO,CAAC,OAAO,CAAC,CAAY;IAC5B,OAAO,CAAC,IAAI,CAAM;IAElB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;gBAElC,GAAG,EAAE,GAAG;IAIpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyChB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CAW7C"}

Some files were not shown because too many files have changed in this diff Show More