Research Date: 2025-10-10 Focus Areas: Model Context Protocol (MCP) session management, Apple platform detection, xcodebuild destination handling
This research explores best practices for:
- MCP server session state management
- Platform-specific builds across iOS, visionOS, macOS, watchOS, tvOS
- Automatic platform detection from simulator UUIDs
- xcodebuild destination specifier patterns
Key Findings:
- MCP sessions require careful security and state lifecycle management
- Platform detection can be automated using
xcrun simctl list --jsonwith runtime identifier parsing - Session defaults provide excellent UX by reducing parameter repetition
- XcodeBuildMCP's current implementation follows many industry best practices
Critical Security Requirements:
-
Session IDs Must Be Secure
- Use secure, non-deterministic session IDs
- Avoid predictable or sequential identifiers
- Use secure random number generators
- Rotate or expire session IDs to reduce risk
-
User-Specific Binding
- Bind session IDs to user-specific information
- Recommended key format:
<user_id>:<session_id> - Combine session ID with internal user ID
-
Authentication Separation
- Sessions must NOT be used for authentication
- Session management is separate from auth mechanisms
Security Anti-Pattern (Specification Warning):
"The specification mandates session IDs in URL parameters for HTTP transport, which violates security best practices by exposing sensitive identifiers in server logs, browser history, and network traffic, enabling session hijacking attacks."
Source: MCP Security Best Practices
Definition:
"In MCP, session management means keeping track of a conversation between your LLM application and your server across multiple requests, without which the app would need to start from scratch with every request."
Implementation Patterns:
-
In-Memory Storage (Simple)
- Works well for most use cases
- Fast and straightforward
- Lost on server restart
- XcodeBuildMCP's current approach
-
Persistent Storage (Advanced)
- Redis for distributed systems
- Database storage for multi-instance setups
- Survives server restarts
- Required for load-balanced deployments
-
Singleton Server Pattern
- Single McpServer instance at startup
- Session state stored in server memory
- All requests for a session routed to same process
- Ideal for single-instance deployments and rapid prototyping
Example Implementation (From Research):
class SessionStore {
private defaults: SessionDefaults = {};
setDefaults(partial: Partial<SessionDefaults>): void {
this.defaults = { ...this.defaults, ...partial };
}
clear(keys?: string[]): void {
if (!keys) {
this.defaults = {};
} else {
keys.forEach(k => delete this.defaults[k]);
}
}
get<K extends keyof SessionDefaults>(key: K): SessionDefaults[K] {
return this.defaults[key];
}
}Phases:
- Initialization: Capability negotiation and state setup
- Operation: Main working phase with stateful requests
- Cleanup: Proper resource disposal and session termination
Best Practices:
- Add timeout mechanisms for abandoned sessions
- Clean up resources on session termination
- Handle server restarts gracefully
- Support session resumption where appropriate
Source: MCP Session Management 2025
Command:
xcrun simctl list devices --jsonJSON Structure:
{
"devices": {
"com.apple.CoreSimulator.SimRuntime.iOS-18-0": [
{
"name": "iPhone 16",
"udid": "E621E1F8-C36C-495A-93FC-0C247A3E6E5F",
"state": "Booted",
"isAvailable": true
}
],
"com.apple.CoreSimulator.SimRuntime.visionOS-2-0": [
{
"name": "Apple Vision Pro",
"udid": "B1234567-89AB-CDEF-0123-456789ABCDEF",
"state": "Shutdown",
"isAvailable": true
}
]
}
}Runtime Identifier Format:
com.apple.CoreSimulator.SimRuntime.{PLATFORM}-{MAJOR}-{MINOR}
Examples:
- com.apple.CoreSimulator.SimRuntime.iOS-18-0
- com.apple.CoreSimulator.SimRuntime.visionOS-2-0
- com.apple.CoreSimulator.SimRuntime.tvOS-18-0
- com.apple.CoreSimulator.SimRuntime.watchOS-11-0
From UUID to Platform:
async function detectPlatformFromUUID(
simulatorUuid: string,
executor: CommandExecutor
): Promise<{ platform: XcodePlatform; runtime: string } | null> {
const result = await executor(
['xcrun', 'simctl', 'list', 'devices', '--json'],
'Detect Platform'
);
if (!result.success) return null;
const data = JSON.parse(result.output);
for (const [runtimeId, devices] of Object.entries(data.devices)) {
const device = devices.find(d => d.udid === simulatorUuid);
if (device) {
// Parse runtime identifier
// com.apple.CoreSimulator.SimRuntime.iOS-18-0 → iOS Simulator
const platform = extractPlatformFromRuntime(runtimeId);
return { platform, runtime: runtimeId };
}
}
return null;
}
function extractPlatformFromRuntime(runtimeId: string): XcodePlatform {
const match = runtimeId.match(/SimRuntime\.(\w+)-/);
if (!match) return XcodePlatform.iOSSimulator; // fallback
const platform = match[1];
switch (platform.toLowerCase()) {
case 'ios': return XcodePlatform.iOSSimulator;
case 'visionos': return XcodePlatform.visionOSSimulator;
case 'tvos': return XcodePlatform.tvOSSimulator;
case 'watchos': return XcodePlatform.watchOSSimulator;
default: return XcodePlatform.iOSSimulator;
}
}XcodeBuildMCP Implementation (Robust):
The current implementation in list_sims.ts handles Apple's known simctl JSON bugs (duplicate runtime IDs in iOS 26.0 beta) by using dual parsing:
function parseTextOutput(textOutput: string): SimulatorDevice[] {
const devices: SimulatorDevice[] = [];
const lines = textOutput.split('\n');
let currentRuntime = '';
for (const line of lines) {
// Match runtime headers like "-- iOS 26.0 --"
const runtimeMatch = line.match(/^-- ([\w\s.]+) --$/);
if (runtimeMatch) {
currentRuntime = runtimeMatch[1];
continue;
}
// Match device lines
const deviceMatch = line.match(
/^\s+(.+?)\s+\(([^)]+)\)\s+\((Booted|Shutdown|Booting)\)/
);
if (deviceMatch && currentRuntime) {
devices.push({
name: deviceMatch[1].trim(),
udid: deviceMatch[2],
state: deviceMatch[3],
runtime: currentRuntime
});
}
}
return devices;
}Benefits:
- Handles Apple's simctl bugs gracefully
- Provides platform information via runtime string
- Merges JSON and text results for maximum reliability
Official Documentation: xcodebuild man page
Supported Platforms:
- macOS
- iOS
- iOS Simulator
- watchOS
- watchOS Simulator
- tvOS
- tvOS Simulator
- visionOS
- visionOS Simulator
- DriverKit
Common Keys:
platform- Target platform (required)name- Device/simulator nameOS- Operating system versionid- Unique device identifier (UUID/UDID)arch- Architecture (arm64, x86_64)
Syntax:
-destination 'key1=value1,key2=value2,...'
iOS Simulator (by UUID):
-destination 'platform=iOS Simulator,id=E621E1F8-C36C-495A-93FC-0C247A3E6E5F'iOS Simulator (by name):
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest'visionOS Simulator (by name):
-destination 'platform=visionOS Simulator,name=Apple Vision Pro,OS=2.0'macOS (with architecture):
-destination 'platform=macOS,arch=arm64'Generic Platform Build:
-destination 'generic/platform=iOS'
-destination 'generic/platform=visionOS'Physical Device (by UDID):
-destination 'platform=iOS,id=00008030-001234567890ABCD'Parallel Testing:
xcodebuild test \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-destination 'platform=iOS Simulator,name=iPhone SE (3rd generation)' \
-scheme MyAppxcodebuild automatically chooses the number of devices to run simultaneously.
Key Finding:
"If -destination is omitted, xcodebuild defaults to a destination compatible with the selected scheme."
Implications:
- Schemes encode default platform targets
- xcodebuild can infer appropriate destination
- Fallback behavior for missing destination parameters
Troubleshooting Pattern:
"When in doubt with destination parameters, xcodebuild will fail with nonsense input and list all available destinations for a given scheme."
# Intentionally fail to see available destinations
xcodebuild -scheme MyApp -destination 'INVALID' buildCurrent Implementation: /Users/dalecarman/Groove Jones Dropbox/Dale Carman/Projects/dev/XcodeBuildMCP/src/utils/session-store.ts
Strengths:
- ✅ Singleton pattern for stateful server
- ✅ Type-safe SessionDefaults interface
- ✅ Granular control (set, clear, get operations)
- ✅ Logging for debugging
- ✅ Supports partial updates
- ✅ Supports clearing specific keys or all
Supported Session Defaults:
type SessionDefaults = {
projectPath?: string;
workspacePath?: string;
scheme?: string;
configuration?: string;
simulatorName?: string;
simulatorId?: string;
deviceId?: string;
useLatestOS?: boolean;
arch?: 'arm64' | 'x86_64';
};Best Practice Alignment:
- ✅ In-memory storage appropriate for single-instance deployment
- ✅ Simple timeout mechanism would be valuable addition
⚠️ No explicit session ID binding (not needed for stdio MCP)⚠️ State lost on restart (acceptable for development tool)
Current Platform Enum:
enum XcodePlatform {
macOS = 'macOS',
iOS = 'iOS',
iOSSimulator = 'iOS Simulator',
watchOS = 'watchOS',
watchOSSimulator = 'watchOS Simulator',
tvOS = 'tvOS',
tvOSSimulator = 'tvOS Simulator',
visionOS = 'visionOS',
visionOSSimulator = 'visionOS Simulator',
}Strengths:
- ✅ Comprehensive platform support
- ✅ Clear distinction between device and simulator
- ✅ String values match xcodebuild expectations
Destination String Construction:
function constructDestinationString(
platform: XcodePlatform,
simulatorName?: string,
simulatorId?: string,
useLatest: boolean = true,
arch?: string,
): string {
// UUID takes precedence for simulators
if (isSimulatorPlatform && simulatorId) {
return `platform=${platform},id=${simulatorId}`;
}
// Name-based with OS version
if (isSimulatorPlatform && simulatorName) {
return `platform=${platform},name=${simulatorName}${useLatest ? ',OS=latest' : ''}`;
}
// Platform-specific handling for devices and macOS
switch (platform) {
case XcodePlatform.macOS:
return arch ? `platform=macOS,arch=${arch}` : 'platform=macOS';
case XcodePlatform.iOS:
return 'generic/platform=iOS';
// ...
}
}Best Practice Alignment:
- ✅ Follows xcodebuild documentation patterns
- ✅ UUID precedence over name (more specific)
- ✅ OS=latest default for simulators
- ✅ Architecture support for macOS
Current Implementation: /Users/dalecarman/Groove Jones Dropbox/Dale Carman/Projects/dev/XcodeBuildMCP/src/utils/simulator-utils.ts
Function: determineSimulatorUuid
Strengths:
- ✅ UUID validation with regex
- ✅ Name-to-UUID resolution via simctl
- ✅ Availability checking (isAvailable flag)
- ✅ Clear error messages
- ✅ Helpful warnings (e.g., name looks like UUID)
Process:
- If UUID provided → use directly
- If name provided but looks like UUID → use as UUID with warning
- If name provided → query simctl and resolve to UUID
- Return detailed error if not found or unavailable
Best Practice Alignment:
- ✅ Defensive validation
- ✅ Clear error messages with actionable guidance
- ✅ JSON parsing of simctl output
- ✅ Availability filtering
Recommendation: Add automatic platform detection from simulator UUID
Implementation:
// src/utils/simulator-utils.ts
export async function detectPlatformFromSimulatorUuid(
simulatorUuid: string,
executor: CommandExecutor,
): Promise<{ platform: XcodePlatform; runtime: string } | { error: string }> {
log('info', `Detecting platform for simulator UUID: ${simulatorUuid}`);
const listResult = await executor(
['xcrun', 'simctl', 'list', 'devices', '--json'],
'Detect Platform from UUID'
);
if (!listResult.success) {
return { error: 'Failed to list simulators for platform detection' };
}
try {
const data = JSON.parse(listResult.output);
for (const [runtimeId, devices] of Object.entries(data.devices)) {
const device = (devices as any[]).find(d => d.udid === simulatorUuid);
if (device) {
const platform = extractPlatformFromRuntimeId(runtimeId);
log('info', `Detected platform: ${platform} from runtime: ${runtimeId}`);
return { platform, runtime: runtimeId };
}
}
return { error: `Simulator with UUID ${simulatorUuid} not found` };
} catch (error) {
return { error: `Failed to parse simulator data: ${error}` };
}
}
function extractPlatformFromRuntimeId(runtimeId: string): XcodePlatform {
// Parse: com.apple.CoreSimulator.SimRuntime.iOS-18-0
const match = runtimeId.match(/SimRuntime\.(\w+)-/);
if (!match) {
log('warn', `Could not parse runtime ID: ${runtimeId}, defaulting to iOS Simulator`);
return XcodePlatform.iOSSimulator;
}
const platform = match[1].toLowerCase();
switch (platform) {
case 'ios':
return XcodePlatform.iOSSimulator;
case 'visionos':
return XcodePlatform.visionOSSimulator;
case 'tvos':
return XcodePlatform.tvOSSimulator;
case 'watchos':
return XcodePlatform.watchOSSimulator;
default:
log('warn', `Unknown platform: ${platform}, defaulting to iOS Simulator`);
return XcodePlatform.iOSSimulator;
}
}Usage in build_sim.ts:
async function build_simLogic(
params: BuildSimulatorParams,
executor: CommandExecutor,
): Promise<ToolResponse> {
// Auto-detect platform if simulator UUID is provided
let platform = XcodePlatform.iOSSimulator; // default
if (params.simulatorId) {
const detection = await detectPlatformFromSimulatorUuid(
params.simulatorId,
executor
);
if ('platform' in detection) {
platform = detection.platform;
log('info', `Auto-detected platform: ${platform}`);
} else {
log('warn', `Platform detection failed: ${detection.error}`);
// Continue with default iOS Simulator
}
}
return executeXcodeBuildCommand(
params,
{
platform, // Use detected platform
simulatorId: params.simulatorId,
simulatorName: params.simulatorName,
// ...
},
// ...
);
}Benefits:
- ✅ Eliminates need for explicit platform parameter
- ✅ Works seamlessly with session defaults
- ✅ Supports all Apple platforms automatically
- ✅ Graceful fallback to iOS Simulator
Recommendation: Add timeout mechanism for abandoned sessions
Implementation:
// src/utils/session-store.ts
class SessionStore {
private defaults: SessionDefaults = {};
private lastAccess: number = Date.now();
private timeoutMs: number = 30 * 60 * 1000; // 30 minutes
setDefaults(partial: Partial<SessionDefaults>): void {
this.defaults = { ...this.defaults, ...partial };
this.lastAccess = Date.now();
log('info', `[Session] Defaults updated: ${Object.keys(partial).join(', ')}`);
}
get<K extends keyof SessionDefaults>(key: K): SessionDefaults[K] {
this.checkTimeout();
this.lastAccess = Date.now();
return this.defaults[key];
}
private checkTimeout(): void {
const elapsed = Date.now() - this.lastAccess;
if (elapsed > this.timeoutMs && Object.keys(this.defaults).length > 0) {
log('info', '[Session] Session timeout reached, clearing defaults');
this.defaults = {};
}
}
}Benefits:
- ✅ Prevents stale session state
- ✅ Automatic cleanup after inactivity
- ✅ Configurable timeout period
Recommendation: Enhance list_sims to group by platform
Current Output:
Available iOS Simulators:
iOS 18.0:
- iPhone 16 (UUID) [Booted]
- iPhone 16 Pro (UUID)
visionOS 2.0:
- Apple Vision Pro (UUID)
Enhanced Output:
Available Simulators by Platform:
📱 iOS Simulators:
iOS 18.0:
- iPhone 16 (UUID) [Booted]
- iPhone 16 Pro (UUID)
🥽 visionOS Simulators:
visionOS 2.0:
- Apple Vision Pro (UUID)
📺 tvOS Simulators:
tvOS 18.0:
- Apple TV 4K (UUID)
⌚ watchOS Simulators:
watchOS 11.0:
- Apple Watch Series 10 (UUID)
Implementation:
// Categorize by platform first, then by runtime
const platformGroups: Record<string, Record<string, SimulatorDevice[]>> = {
'iOS': {},
'visionOS': {},
'tvOS': {},
'watchOS': {}
};
for (const [runtime, devices] of Object.entries(allDevices)) {
const platform = extractPlatformFromRuntimeString(runtime);
if (!platformGroups[platform]) {
platformGroups[platform] = {};
}
platformGroups[platform][runtime] = devices.filter(d => d.isAvailable);
}
// Format output with platform grouping and emoji indicators
let responseText = 'Available Simulators by Platform:\n\n';
for (const [platform, runtimes] of Object.entries(platformGroups)) {
if (Object.keys(runtimes).length === 0) continue;
const emoji = getPlatformEmoji(platform);
responseText += `${emoji} ${platform} Simulators:\n`;
// ... format devices per runtime
}Benefits:
- ✅ Clear platform separation
- ✅ Visual indicators for platform types
- ✅ Easier navigation for multi-platform projects
- ✅ Supports visionOS workflows explicitly
Recommendation: Add platform to session defaults
Implementation:
// src/utils/session-store.ts
export type SessionDefaults = {
projectPath?: string;
workspacePath?: string;
scheme?: string;
configuration?: string;
simulatorName?: string;
simulatorId?: string;
deviceId?: string;
useLatestOS?: boolean;
arch?: 'arm64' | 'x86_64';
platform?: XcodePlatform; // NEW
};Usage:
// User sets platform for visionOS development
session_set({ platform: 'visionOS Simulator' });
// Subsequent build commands use visionOS platform automatically
build_sim({ scheme: 'MyVisionApp' });
// → Internally resolves to platform=visionOS SimulatorBenefits:
- ✅ Explicit platform selection for visionOS/tvOS projects
- ✅ Overrides auto-detection when needed
- ✅ Consistent with other session defaults pattern
From Research:
-
Stateless vs Stateful Servers
- Stateless: No session persistence, every request independent
- Stateful: Session state maintained across requests
- XcodeBuildMCP: Stateful approach appropriate for development workflow
-
Singleton Server Pattern
- One McpServer instance at startup
- Authoritative source for application state
- Ideal for single-instance deployments
- XcodeBuildMCP: Follows this pattern effectively
-
Session Persistence Strategies
- Memory: Fast, simple, lost on restart (XcodeBuildMCP's approach)
- Knowledge Graph: Structured memory across sessions (claude-flow example)
- Database: Multi-instance, survives restarts
Source: MCP Tools Cookbook
Progressive Information Gathering:
"Essential MCP tool patterns include: code reviewer prompts, dynamic ReAct pattern prompt generation, progressive information gathering with intelligent questions, sequential tool dependencies for complex workflows requiring state management and ordered execution"
Applied to XcodeBuildMCP:
- ✅ Session defaults enable progressive parameter gathering
- ✅ Sequential workflow: set defaults → build → test → deploy
- ✅ State management for complex multi-step operations
From Apple Developer Forums and Stack Overflow:
-
Always Use JSON Output
xcrun simctl list devices --json
- Structured data easier to parse
- Consistent format across Xcode versions
- Better error handling
-
Filter by Availability
- Only show simulators with
isAvailable: true - Avoid errors from unavailable runtimes
- Clear user experience
- Only show simulators with
-
Handle Apple Bugs Gracefully
- Dual JSON + text parsing (XcodeBuildMCP does this!)
- Fallback strategies for malformed output
- Logging for debugging
-
UUID Validation
- Regex check before API calls
- Early validation prevents wasted operations
- Clear error messages
Repository: modelcontextprotocol/servers
Session Management Patterns Observed:
-
Memory Server
- Knowledge graph-based persistent memory
- Structured information across sessions
- Query capabilities for stored data
-
GitHub Server
- Stateless design for API operations
- No session persistence
- Token-based authentication separate from sessions
-
SQLite Server
- Database as session state store
- Persistent across server restarts
- Transaction-based state management
Relevance to XcodeBuildMCP:
- ✅ Memory-based state appropriate for development tool
- ✅ Session defaults similar to knowledge graph pattern
⚠️ Consider database persistence for production use cases
Pattern: Persistent conversation memory
Features:
- Conversation history across sessions
- Project state persistence
- User preference storage
Applied to XcodeBuildMCP:
- Session defaults = project state
- Configuration = user preferences
- Potential for conversation history (build logs)
XcodeBuildMCP demonstrates strong adherence to MCP session management best practices with its SessionStore implementation. The platform handling foundation is solid, but could be enhanced with:
- Automatic platform detection from simulator UUIDs
- Session timeout management for abandoned sessions
- Platform-aware list_sims output for multi-platform projects
- Platform in session defaults for explicit visionOS/tvOS workflows
These enhancements would:
- Reduce user friction in multi-platform projects
- Improve visionOS and tvOS development experience
- Maintain compatibility with existing iOS workflows
- Follow industry best practices for MCP servers
The current implementation provides an excellent foundation for these improvements while maintaining stability and predictability for users.
Research compiled by: Claude (Anthropic) Model: Claude Sonnet 4.5 Date: 2025-10-10