[Add] Synaptic AI Pro
https://assetstore.unity.com/packages/tools/generative-ai/synaptic-ai-pro-natural-language-control-for-unity-336030
This commit is contained in:
@@ -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:
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 543db41d8b038498aa18932b924a0295
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 948f0b8ef1e4f4e5ca74a34c75a825a6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+21
@@ -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.
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+1323
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 533872ecd915544f18b92f579e30bc8b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d54b70b24465143f5b696c3ccdaf955b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Generated
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=cli.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":""}
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+135
@@ -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
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6213b24c68d8846fcb1a93be66a16319
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Generated
Vendored
+248
@@ -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
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/client/auth.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+718
@@ -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
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+14
@@ -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
|
||||
Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js.meta
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+1497
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+295
@@ -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
|
||||
Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/index.js.map
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+169
@@ -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
|
||||
Generated
Vendored
+1
@@ -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"}
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+256
@@ -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
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../src/client/middleware.ts"],"names":[],"mappings":";;;AAAA,uCAKmB;AASnB;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACI,MAAM,SAAS,GACpB,CAAC,QAA6B,EAAE,OAAsB,EAAc,EAAE,CACtE,CAAC,IAAI,EAAE,EAAE;IACP,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,WAAW,GAAG,KAAK,IAAuB,EAAE;YAChD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,OAAO,CAAC,CAAC;YAE3C,mDAAmD;YACnD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC;YACvC,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;YAChE,CAAC;YAED,OAAO,MAAM,IAAI,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACjD,CAAC,CAAC;QAEF,IAAI,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAC;QAEnC,uDAAuD;QACvD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACH,MAAM,mBAAmB,GAAG,IAAA,oCAA0B,EAAC,QAAQ,CAAC,CAAC;gBAEjE,mDAAmD;gBACnD,MAAM,SAAS,GACb,OAAO;oBACP,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBAErE,MAAM,MAAM,GAAG,MAAM,IAAA,cAAI,EAAC,QAAQ,EAAE;oBAClC,SAAS;oBACT,mBAAmB;oBACnB,OAAO,EAAE,IAAI;iBACd,CAAC,CAAC;gBAEH,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;oBAC1B,MAAM,IAAI,2BAAiB,CACzB,iEAAiE,CAClE,CAAC;gBACJ,CAAC;gBAED,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC;oBAC5B,MAAM,IAAI,2BAAiB,CACzB,sCAAsC,MAAM,EAAE,CAC/C,CAAC;gBACJ,CAAC;gBAED,sCAAsC;gBACtC,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAC;YACjC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,KAAK,YAAY,2BAAiB,EAAE,CAAC;oBACvC,MAAM,KAAK,CAAC;gBACd,CAAC;gBACD,MAAM,IAAI,2BAAiB,CACzB,8BAA8B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACvF,CAAC;YACJ,CAAC;QACH,CAAC;QAED,+DAA+D;QAC/D,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YACjE,MAAM,IAAI,2BAAiB,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC;QAClE,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC,CAAC;AAlES,QAAA,SAAS,aAkElB;AA6CJ;;;;;;;;;;;;;;;GAeG;AACI,MAAM,WAAW,GAAG,CAAC,UAA0B,EAAE,EAAc,EAAE;IACtE,MAAM,EACJ,MAAM,EACN,qBAAqB,GAAG,KAAK,EAC7B,sBAAsB,GAAG,KAAK,EAC9B,WAAW,GAAG,CAAC,GAChB,GAAG,OAAO,CAAC;IAEZ,MAAM,aAAa,GAAkB,CAAC,KAAK,EAAE,EAAE;QAC7C,MAAM,EACJ,MAAM,EACN,GAAG,EACH,MAAM,EACN,UAAU,EACV,QAAQ,EACR,cAAc,EACd,eAAe,EACf,KAAK,GACN,GAAG,KAAK,CAAC;QAEV,IAAI,OAAO,GAAG,KAAK;YACjB,CAAC,CAAC,QAAQ,MAAM,IAAI,GAAG,YAAY,KAAK,CAAC,OAAO,KAAK,QAAQ,KAAK;YAClE,CAAC,CAAC,QAAQ,MAAM,IAAI,GAAG,IAAI,MAAM,IAAI,UAAU,KAAK,QAAQ,KAAK,CAAC;QAEpE,sCAAsC;QACtC,IAAI,qBAAqB,IAAI,cAAc,EAAE,CAAC;YAC5C,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;iBACpD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,KAAK,EAAE,CAAC;iBACzC,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,OAAO,IAAI,yBAAyB,UAAU,GAAG,CAAC;QACpD,CAAC;QAED,IAAI,sBAAsB,IAAI,eAAe,EAAE,CAAC;YAC9C,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC;iBACrD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,KAAK,EAAE,CAAC;iBACzC,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,OAAO,IAAI,0BAA0B,UAAU,GAAG,CAAC;QACrD,CAAC;QAED,IAAI,KAAK,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;YAC3B,sCAAsC;YACtC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,sCAAsC;YACtC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,IAAI,aAAa,CAAC;IAEtC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACrC,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,KAAI,KAAK,CAAC;QACrC,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACjE,MAAM,cAAc,GAAG,qBAAqB;YAC1C,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,OAAO,CAAC;YAC5B,CAAC,CAAC,SAAS,CAAC;QAEd,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YACzC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAE/C,mDAAmD;YACnD,IAAI,QAAQ,CAAC,MAAM,IAAI,WAAW,EAAE,CAAC;gBACnC,KAAK,CAAC;oBACJ,MAAM;oBACN,GAAG;oBACH,MAAM,EAAE,QAAQ,CAAC,MAAM;oBACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;oBAC/B,QAAQ;oBACR,cAAc;oBACd,eAAe,EAAE,sBAAsB;wBACrC,CAAC,CAAC,QAAQ,CAAC,OAAO;wBAClB,CAAC,CAAC,SAAS;iBACd,CAAC,CAAC;YACL,CAAC;YAED,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAE/C,4CAA4C;YAC5C,KAAK,CAAC;gBACJ,MAAM;gBACN,GAAG;gBACH,MAAM,EAAE,CAAC;gBACT,UAAU,EAAE,eAAe;gBAC3B,QAAQ;gBACR,cAAc;gBACd,KAAK,EAAE,KAAc;aACtB,CAAC,CAAC;YAEH,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AA/FW,QAAA,WAAW,eA+FtB;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACI,MAAM,gBAAgB,GAAG,CAC9B,GAAG,UAAwB,EACf,EAAE;IACd,OAAO,CAAC,IAAI,EAAE,EAAE;QACd,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;IAC/D,CAAC,CAAC;AACJ,CAAC,CAAC;AANW,QAAA,gBAAgB,oBAM3B;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACI,MAAM,gBAAgB,GAAG,CAC9B,OAIsB,EACV,EAAE;IACd,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,KAAqB,EAAE,IAAI,CAAC,CAAC;AAC/E,CAAC,CAAC;AARW,QAAA,gBAAgB,oBAQ3B"}
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+78
@@ -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
|
||||
Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.d.ts.map
Generated
Vendored
+1
@@ -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"}
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+189
@@ -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
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+78
@@ -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
|
||||
Generated
Vendored
+1
@@ -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"}
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+184
@@ -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
|
||||
Assets/Synaptic AI Pro/MCPServer/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/stdio.js.map
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../../src/client/stdio.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+156
@@ -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
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"streamableHttp.d.ts","sourceRoot":"","sources":["../../../src/client/streamableHttp.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+380
@@ -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
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+14
@@ -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
|
||||
Generated
Vendored
+17
@@ -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
|
||||
Generated
Vendored
+1
@@ -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
Reference in New Issue
Block a user